/*-------------------------------------------------------------------------
 * drawElements Quality Program Tester Core
 * ----------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Texture lookup simulator that is capable of verifying generic
 *		  lookup results based on accuracy parameters.
 *//*--------------------------------------------------------------------*/

#include "tcuTexLookupVerifier.hpp"
#include "tcuTexVerifierUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "tcuTextureUtil.hpp"
#include "deMath.h"

namespace tcu
{

using namespace TexVerifierUtil;

// Generic utilities

#if defined(DE_DEBUG)
static bool isSamplerSupported (const Sampler& sampler)
{
	return sampler.compare == Sampler::COMPAREMODE_NONE &&
		   isWrapModeSupported(sampler.wrapS)			&&
		   isWrapModeSupported(sampler.wrapT)			&&
		   isWrapModeSupported(sampler.wrapR);
}
#endif // DE_DEBUG

// Color read & compare utilities

static inline bool coordsInBounds (const ConstPixelBufferAccess& access, int x, int y, int z)
{
	return de::inBounds(x, 0, access.getWidth()) && de::inBounds(y, 0, access.getHeight()) && de::inBounds(z, 0, access.getDepth());
}

template<typename ScalarType>
inline Vector<ScalarType, 4> lookup (const ConstPixelBufferAccess& access, const Sampler& sampler, int i, int j, int k)
{
	if (coordsInBounds(access, i, j, k))
		return access.getPixelT<ScalarType>(i, j, k);
	else
		return sampleTextureBorder<ScalarType>(access.getFormat(), sampler);
}

template<>
inline Vector<float, 4> lookup (const ConstPixelBufferAccess& access, const Sampler& sampler, int i, int j, int k)
{
	// Specialization for float lookups: sRGB conversion is performed as specified in format.
	if (coordsInBounds(access, i, j, k))
	{
		const Vec4 p = access.getPixel(i, j, k);
		return isSRGB(access.getFormat()) ? sRGBToLinear(p) : p;
	}
	else
		return sampleTextureBorder<float>(access.getFormat(), sampler);
}

static inline bool isColorValid (const LookupPrecision& prec, const Vec4& ref, const Vec4& result)
{
	const Vec4 diff = abs(ref - result);
	return boolAll(logicalOr(lessThanEqual(diff, prec.colorThreshold), logicalNot(prec.colorMask)));
}

static inline bool isColorValid (const IntLookupPrecision& prec, const IVec4& ref, const IVec4& result)
{
	return boolAll(logicalOr(lessThanEqual(absDiff(ref, result).asUint(), prec.colorThreshold), logicalNot(prec.colorMask)));
}

static inline bool isColorValid (const IntLookupPrecision& prec, const UVec4& ref, const UVec4& result)
{
	return boolAll(logicalOr(lessThanEqual(absDiff(ref, result), prec.colorThreshold), logicalNot(prec.colorMask)));
}

struct ColorQuad
{
	Vec4	p00;		//!< (0, 0)
	Vec4	p01;		//!< (1, 0)
	Vec4	p10;		//!< (0, 1)
	Vec4	p11;		//!< (1, 1)
};

static void lookupQuad (ColorQuad& dst, const ConstPixelBufferAccess& level, const Sampler& sampler, int x0, int x1, int y0, int y1, int z)
{
	dst.p00	= lookup<float>(level, sampler, x0, y0, z);
	dst.p10	= lookup<float>(level, sampler, x1, y0, z);
	dst.p01	= lookup<float>(level, sampler, x0, y1, z);
	dst.p11	= lookup<float>(level, sampler, x1, y1, z);
}

struct ColorLine
{
	Vec4	p0;		//!< 0
	Vec4	p1;		//!< 1
};

static void lookupLine (ColorLine& dst, const ConstPixelBufferAccess& level, const Sampler& sampler, int x0, int x1, int y)
{
	dst.p0 = lookup<float>(level, sampler, x0, y, 0);
	dst.p1 = lookup<float>(level, sampler, x1, y, 0);
}

template<typename T, int Size>
static T minComp (const Vector<T, Size>& vec)
{
	T minVal = vec[0];
	for (int ndx = 1; ndx < Size; ndx++)
		minVal = de::min(minVal, vec[ndx]);
	return minVal;
}

template<typename T, int Size>
static T maxComp (const Vector<T, Size>& vec)
{
	T maxVal = vec[0];
	for (int ndx = 1; ndx < Size; ndx++)
		maxVal = de::max(maxVal, vec[ndx]);
	return maxVal;
}

static float computeBilinearSearchStepFromFloatLine (const LookupPrecision&	prec,
													 const ColorLine&		line)
{
	DE_ASSERT(boolAll(greaterThan(prec.colorThreshold, Vec4(0.0f))));

	const int		maxSteps	= 1<<16;
	const Vec4		d			= abs(line.p1 - line.p0);
	const Vec4		stepCount	= d / prec.colorThreshold;
	const Vec4		minStep		= 1.0f / (stepCount + 1.0f);
	const float		step		= de::max(minComp(minStep), 1.0f / float(maxSteps));

	return step;
}

static float computeBilinearSearchStepFromFloatQuad (const LookupPrecision&	prec,
													 const ColorQuad&		quad)
{
	DE_ASSERT(boolAll(greaterThan(prec.colorThreshold, Vec4(0.0f))));

	const int		maxSteps	= 1<<16;
	const Vec4		d0			= abs(quad.p10 - quad.p00);
	const Vec4		d1			= abs(quad.p01 - quad.p00);
	const Vec4		d2			= abs(quad.p11 - quad.p10);
	const Vec4		d3			= abs(quad.p11 - quad.p01);
	const Vec4		maxD		= max(d0, max(d1, max(d2, d3)));
	const Vec4		stepCount	= maxD / prec.colorThreshold;
	const Vec4		minStep		= 1.0f / (stepCount + 1.0f);
	const float		step		= de::max(minComp(minStep), 1.0f / float(maxSteps));

	return step;
}

static float computeBilinearSearchStepForUnorm (const LookupPrecision& prec)
{
	DE_ASSERT(boolAll(greaterThan(prec.colorThreshold, Vec4(0.0f))));

	const Vec4		stepCount	= 1.0f / prec.colorThreshold;
	const Vec4		minStep		= 1.0f / (stepCount + 1.0f);
	const float		step		= minComp(minStep);

	return step;
}

static float computeBilinearSearchStepForSnorm (const LookupPrecision& prec)
{
	DE_ASSERT(boolAll(greaterThan(prec.colorThreshold, Vec4(0.0f))));

	const Vec4		stepCount	= 2.0f / prec.colorThreshold;
	const Vec4		minStep		= 1.0f / (stepCount + 1.0f);
	const float		step		= minComp(minStep);

	return step;
}

static inline Vec4 min (const ColorLine& line)
{
	return min(line.p0, line.p1);
}

static inline Vec4 max (const ColorLine& line)
{
	return max(line.p0, line.p1);
}

static inline Vec4 min (const ColorQuad& quad)
{
	return min(quad.p00, min(quad.p10, min(quad.p01, quad.p11)));
}

static inline Vec4 max (const ColorQuad& quad)
{
	return max(quad.p00, max(quad.p10, max(quad.p01, quad.p11)));
}

static bool isInColorBounds (const LookupPrecision& prec, const ColorQuad& quad, const Vec4& result)
{
	const tcu::Vec4 minVal = min(quad) - prec.colorThreshold;
	const tcu::Vec4 maxVal = max(quad) + prec.colorThreshold;
	return boolAll(logicalOr(logicalAnd(greaterThanEqual(result, minVal), lessThanEqual(result, maxVal)), logicalNot(prec.colorMask)));
}

static bool isInColorBounds (const LookupPrecision& prec, const ColorQuad& quad0, const ColorQuad& quad1, const Vec4& result)
{
	const tcu::Vec4 minVal = min(min(quad0), min(quad1)) - prec.colorThreshold;
	const tcu::Vec4 maxVal = max(max(quad0), max(quad1)) + prec.colorThreshold;
	return boolAll(logicalOr(logicalAnd(greaterThanEqual(result, minVal), lessThanEqual(result, maxVal)), logicalNot(prec.colorMask)));
}

static bool isInColorBounds (const LookupPrecision& prec, const ColorLine& line0, const ColorLine& line1, const Vec4& result)
{
	const tcu::Vec4 minVal = min(min(line0), min(line1)) - prec.colorThreshold;
	const tcu::Vec4 maxVal = max(max(line0), max(line1)) + prec.colorThreshold;
	return boolAll(logicalOr(logicalAnd(greaterThanEqual(result, minVal), lessThanEqual(result, maxVal)), logicalNot(prec.colorMask)));
}

static bool isInColorBounds (const LookupPrecision&		prec,
							 const ColorQuad&			quad00,
							 const ColorQuad&			quad01,
							 const ColorQuad&			quad10,
							 const ColorQuad&			quad11,
							 const Vec4&				result)
{
	const tcu::Vec4 minVal = min(min(quad00), min(min(quad01), min(min(quad10), min(quad11)))) - prec.colorThreshold;
	const tcu::Vec4 maxVal = max(max(quad00), max(max(quad01), max(max(quad10), max(quad11)))) + prec.colorThreshold;
	return boolAll(logicalOr(logicalAnd(greaterThanEqual(result, minVal), lessThanEqual(result, maxVal)), logicalNot(prec.colorMask)));
}

// Range search utilities

static bool isLinearRangeValid (const LookupPrecision&	prec,
								const Vec4&				c0,
								const Vec4&				c1,
								const Vec2&				fBounds,
								const Vec4&				result)
{
	// This is basically line segment - AABB test. Valid interpolation line is checked
	// against result AABB constructed by applying threshold.

	const Vec4		i0				= c0*(1.0f - fBounds[0]) + c1*fBounds[0];
	const Vec4		i1				= c0*(1.0f - fBounds[1]) + c1*fBounds[1];
	const Vec4		rMin			= result - prec.colorThreshold;
	const Vec4		rMax			= result + prec.colorThreshold;
	bool			allIntersect	= true;

	// Algorithm: For each component check whether segment endpoints are inside, or intersect with slab.
	// If all intersect or are inside, line segment intersects the whole 4D AABB.
	for (int compNdx = 0; compNdx < 4; compNdx++)
	{
		if (!prec.colorMask[compNdx])
			continue;

		// Signs for both bounds: false = left, true = right.
		const bool	sMin0	= i0[compNdx] >= rMin[compNdx];
		const bool	sMin1	= i1[compNdx] >= rMin[compNdx];
		const bool	sMax0	= i0[compNdx] > rMax[compNdx];
		const bool	sMax1	= i1[compNdx] > rMax[compNdx];

		// If all signs are equal, line segment is outside bounds.
		if (sMin0 == sMin1 && sMin1 == sMax0 && sMax0 == sMax1)
		{
			allIntersect = false;
			break;
		}
	}

	return allIntersect;
}

static bool isBilinearRangeValid (const LookupPrecision&	prec,
								  const ColorQuad&			quad,
								  const Vec2&				xBounds,
								  const Vec2&				yBounds,
								  const float				searchStep,
								  const Vec4&				result)
{
	DE_ASSERT(xBounds.x() <= xBounds.y());
	DE_ASSERT(yBounds.x() <= yBounds.y());
	DE_ASSERT(xBounds.x() + searchStep > xBounds.x()); // step is not effectively 0
	DE_ASSERT(xBounds.y() + searchStep > xBounds.y());

	if (!isInColorBounds(prec, quad, result))
		return false;

	for (float x = xBounds.x(); x < xBounds.y()+searchStep; x += searchStep)
	{
		const float		a	= de::min(x, xBounds.y());
		const Vec4		c0	= quad.p00*(1.0f - a) + quad.p10*a;
		const Vec4		c1	= quad.p01*(1.0f - a) + quad.p11*a;

		if (isLinearRangeValid(prec, c0, c1, yBounds, result))
			return true;
	}

	return false;
}

static bool isTrilinearRangeValid (const LookupPrecision&	prec,
								   const ColorQuad&			quad0,
								   const ColorQuad&			quad1,
								   const Vec2&				xBounds,
								   const Vec2&				yBounds,
								   const Vec2&				zBounds,
								   const float				searchStep,
								   const Vec4&				result)
{
	DE_ASSERT(xBounds.x() <= xBounds.y());
	DE_ASSERT(yBounds.x() <= yBounds.y());
	DE_ASSERT(zBounds.x() <= zBounds.y());
	DE_ASSERT(xBounds.x() + searchStep > xBounds.x()); // step is not effectively 0
	DE_ASSERT(xBounds.y() + searchStep > xBounds.y());
	DE_ASSERT(yBounds.x() + searchStep > yBounds.x());
	DE_ASSERT(yBounds.y() + searchStep > yBounds.y());

	if (!isInColorBounds(prec, quad0, quad1, result))
		return false;

	for (float x = xBounds.x(); x < xBounds.y()+searchStep; x += searchStep)
	{
		for (float y = yBounds.x(); y < yBounds.y()+searchStep; y += searchStep)
		{
			const float		a	= de::min(x, xBounds.y());
			const float		b	= de::min(y, yBounds.y());
			const Vec4		c0	= quad0.p00*(1.0f-a)*(1.0f-b) + quad0.p10*a*(1.0f-b) + quad0.p01*(1.0f-a)*b + quad0.p11*a*b;
			const Vec4		c1	= quad1.p00*(1.0f-a)*(1.0f-b) + quad1.p10*a*(1.0f-b) + quad1.p01*(1.0f-a)*b + quad1.p11*a*b;

			if (isLinearRangeValid(prec, c0, c1, zBounds, result))
				return true;
		}
	}

	return false;
}

static bool isReductionValid (const LookupPrecision&		prec,
							  const Vec4&					c0,
							  const Vec4&					c1,
							  tcu::Sampler::ReductionMode	reductionMode,
							  const Vec4&					result)
{
	DE_ASSERT(reductionMode == tcu::Sampler::MIN || reductionMode == tcu::Sampler::MAX);

	const Vec4 color = (reductionMode == tcu::Sampler::MIN ? tcu::min(c0, c1) : tcu::max(c0, c1));

	return isColorValid(prec, color, result);
}

static bool isReductionValid (const LookupPrecision&		prec,
							  const ColorQuad&				quad,
							  tcu::Sampler::ReductionMode	reductionMode,
							  const Vec4&					result)
{
	DE_ASSERT(reductionMode == tcu::Sampler::MIN || reductionMode == tcu::Sampler::MAX);

	const Vec4 c0 = (reductionMode == tcu::Sampler::MIN ? tcu::min(quad.p00, quad.p01) : tcu::max(quad.p00, quad.p01));
	const Vec4 c1 = (reductionMode == tcu::Sampler::MIN ? tcu::min(quad.p10, quad.p11) : tcu::max(quad.p10, quad.p11));

	return isReductionValid(prec, c0, c1, reductionMode, result);
}

static bool isReductionValid (const LookupPrecision&		prec,
							  const ColorQuad&				quad0,
							  const ColorQuad&				quad1,
							  tcu::Sampler::ReductionMode	reductionMode,
							  const Vec4&					result)
{
	DE_ASSERT(reductionMode == tcu::Sampler::MIN || reductionMode == tcu::Sampler::MAX);

	const ColorQuad quad =
	{
		reductionMode == tcu::Sampler::MIN ? tcu::min(quad0.p00, quad1.p00) : tcu::max(quad0.p00, quad1.p00),			// p00
		reductionMode == tcu::Sampler::MIN ? tcu::min(quad0.p01, quad1.p01) : tcu::max(quad0.p01, quad1.p01),			// p01
		reductionMode == tcu::Sampler::MIN ? tcu::min(quad0.p10, quad1.p10) : tcu::max(quad0.p10, quad1.p10),			// p10
		reductionMode == tcu::Sampler::MIN ? tcu::min(quad0.p11, quad1.p11) : tcu::max(quad0.p11, quad1.p11),			// p11
	};

	return isReductionValid(prec, quad, reductionMode, result);
}

static bool is1DTrilinearFilterResultValid (const LookupPrecision&	prec,
											const ColorLine&		line0,
											const ColorLine&		line1,
											const Vec2&				xBounds0,
											const Vec2&				xBounds1,
											const Vec2&				zBounds,
											const float				searchStep,
											const Vec4&				result)
{
	DE_ASSERT(xBounds0.x() <= xBounds0.y());
	DE_ASSERT(xBounds1.x() <= xBounds1.y());
	DE_ASSERT(xBounds0.x() + searchStep > xBounds0.x()); // step is not effectively 0
	DE_ASSERT(xBounds0.y() + searchStep > xBounds0.y());
	DE_ASSERT(xBounds1.x() + searchStep > xBounds1.x());
	DE_ASSERT(xBounds1.y() + searchStep > xBounds1.y());

	if (!isInColorBounds(prec, line0, line1, result))
		return false;

	for (float x0 = xBounds0.x(); x0 < xBounds0.y()+searchStep; x0 += searchStep)
	{
		const float		a0	= de::min(x0, xBounds0.y());
		const Vec4		c0	= line0.p0*(1.0f-a0) + line0.p1*a0;

		for (float x1 = xBounds1.x(); x1 <= xBounds1.y(); x1 += searchStep)
		{
			const float		a1	= de::min(x1, xBounds1.y());
			const Vec4		c1	= line1.p0*(1.0f-a1) + line1.p1*a1;

			if (isLinearRangeValid(prec, c0, c1, zBounds, result))
				return true;
		}
	}

	return false;
}

static bool is2DTrilinearFilterResultValid (const LookupPrecision&	prec,
											const ColorQuad&		quad0,
											const ColorQuad&		quad1,
											const Vec2&				xBounds0,
											const Vec2&				yBounds0,
											const Vec2&				xBounds1,
											const Vec2&				yBounds1,
											const Vec2&				zBounds,
											const float				searchStep,
											const Vec4&				result)
{
	DE_ASSERT(xBounds0.x() <= xBounds0.y());
	DE_ASSERT(yBounds0.x() <= yBounds0.y());
	DE_ASSERT(xBounds1.x() <= xBounds1.y());
	DE_ASSERT(yBounds1.x() <= yBounds1.y());
	DE_ASSERT(xBounds0.x() + searchStep > xBounds0.x()); // step is not effectively 0
	DE_ASSERT(xBounds0.y() + searchStep > xBounds0.y());
	DE_ASSERT(yBounds0.x() + searchStep > yBounds0.x());
	DE_ASSERT(yBounds0.y() + searchStep > yBounds0.y());
	DE_ASSERT(xBounds1.x() + searchStep > xBounds1.x());
	DE_ASSERT(xBounds1.y() + searchStep > xBounds1.y());
	DE_ASSERT(yBounds1.x() + searchStep > yBounds1.x());
	DE_ASSERT(yBounds1.y() + searchStep > yBounds1.y());

	if (!isInColorBounds(prec, quad0, quad1, result))
		return false;

	for (float x0 = xBounds0.x(); x0 < xBounds0.y()+searchStep; x0 += searchStep)
	{
		for (float y0 = yBounds0.x(); y0 < yBounds0.y()+searchStep; y0 += searchStep)
		{
			const float		a0	= de::min(x0, xBounds0.y());
			const float		b0	= de::min(y0, yBounds0.y());
			const Vec4		c0	= quad0.p00*(1.0f-a0)*(1.0f-b0) + quad0.p10*a0*(1.0f-b0) + quad0.p01*(1.0f-a0)*b0 + quad0.p11*a0*b0;

			for (float x1 = xBounds1.x(); x1 <= xBounds1.y(); x1 += searchStep)
			{
				for (float y1 = yBounds1.x(); y1 <= yBounds1.y(); y1 += searchStep)
				{
					const float		a1	= de::min(x1, xBounds1.y());
					const float		b1	= de::min(y1, yBounds1.y());
					const Vec4		c1	= quad1.p00*(1.0f-a1)*(1.0f-b1) + quad1.p10*a1*(1.0f-b1) + quad1.p01*(1.0f-a1)*b1 + quad1.p11*a1*b1;

					if (isLinearRangeValid(prec, c0, c1, zBounds, result))
						return true;
				}
			}
		}
	}

	return false;
}

static bool is3DTrilinearFilterResultValid (const LookupPrecision&	prec,
											const ColorQuad&		quad00,
											const ColorQuad&		quad01,
											const ColorQuad&		quad10,
											const ColorQuad&		quad11,
											const Vec2&				xBounds0,
											const Vec2&				yBounds0,
											const Vec2&				zBounds0,
											const Vec2&				xBounds1,
											const Vec2&				yBounds1,
											const Vec2&				zBounds1,
											const Vec2&				wBounds,
											const float				searchStep,
											const Vec4&				result)
{
	DE_ASSERT(xBounds0.x() <= xBounds0.y());
	DE_ASSERT(yBounds0.x() <= yBounds0.y());
	DE_ASSERT(zBounds0.x() <= zBounds0.y());
	DE_ASSERT(xBounds1.x() <= xBounds1.y());
	DE_ASSERT(yBounds1.x() <= yBounds1.y());
	DE_ASSERT(zBounds1.x() <= zBounds1.y());
	DE_ASSERT(xBounds0.x() + searchStep > xBounds0.x()); // step is not effectively 0
	DE_ASSERT(xBounds0.y() + searchStep > xBounds0.y());
	DE_ASSERT(yBounds0.x() + searchStep > yBounds0.x());
	DE_ASSERT(yBounds0.y() + searchStep > yBounds0.y());
	DE_ASSERT(zBounds0.x() + searchStep > zBounds0.x());
	DE_ASSERT(zBounds0.y() + searchStep > zBounds0.y());
	DE_ASSERT(xBounds1.x() + searchStep > xBounds1.x());
	DE_ASSERT(xBounds1.y() + searchStep > xBounds1.y());
	DE_ASSERT(yBounds1.x() + searchStep > yBounds1.x());
	DE_ASSERT(yBounds1.y() + searchStep > yBounds1.y());
	DE_ASSERT(zBounds1.x() + searchStep > zBounds1.x());
	DE_ASSERT(zBounds1.y() + searchStep > zBounds1.y());

	if (!isInColorBounds(prec, quad00, quad01, quad10, quad11, result))
		return false;

	for (float x0 = xBounds0.x(); x0 < xBounds0.y()+searchStep; x0 += searchStep)
	{
		for (float y0 = yBounds0.x(); y0 < yBounds0.y()+searchStep; y0 += searchStep)
		{
			const float		a0	= de::min(x0, xBounds0.y());
			const float		b0	= de::min(y0, yBounds0.y());
			const Vec4		c00	= quad00.p00*(1.0f-a0)*(1.0f-b0) + quad00.p10*a0*(1.0f-b0) + quad00.p01*(1.0f-a0)*b0 + quad00.p11*a0*b0;
			const Vec4		c01	= quad01.p00*(1.0f-a0)*(1.0f-b0) + quad01.p10*a0*(1.0f-b0) + quad01.p01*(1.0f-a0)*b0 + quad01.p11*a0*b0;

			for (float z0 = zBounds0.x(); z0 < zBounds0.y()+searchStep; z0 += searchStep)
			{
				const float		c0	= de::min(z0, zBounds0.y());
				const Vec4		cz0	= c00*(1.0f-c0) + c01*c0;

				for (float x1 = xBounds1.x(); x1 < xBounds1.y()+searchStep; x1 += searchStep)
				{
					for (float y1 = yBounds1.x(); y1 < yBounds1.y()+searchStep; y1 += searchStep)
					{
						const float		a1	= de::min(x1, xBounds1.y());
						const float		b1	= de::min(y1, yBounds1.y());
						const Vec4		c10	= quad10.p00*(1.0f-a1)*(1.0f-b1) + quad10.p10*a1*(1.0f-b1) + quad10.p01*(1.0f-a1)*b1 + quad10.p11*a1*b1;
						const Vec4		c11	= quad11.p00*(1.0f-a1)*(1.0f-b1) + quad11.p10*a1*(1.0f-b1) + quad11.p01*(1.0f-a1)*b1 + quad11.p11*a1*b1;

						for (float z1 = zBounds1.x(); z1 < zBounds1.y()+searchStep; z1 += searchStep)
						{
							const float		c1	= de::min(z1, zBounds1.y());
							const Vec4		cz1	= c10*(1.0f - c1) + c11*c1;

							if (isLinearRangeValid(prec, cz0, cz1, wBounds, result))
								return true;
						}
					}
				}
			}
		}
	}

	return false;
}

template<typename PrecType, typename ScalarType>
static bool isNearestSampleResultValid (const ConstPixelBufferAccess&		level,
										const Sampler&						sampler,
										const PrecType&						prec,
										const float							coordX,
										const int							coordY,
										const Vector<ScalarType, 4>&		result)
{
	DE_ASSERT(level.getDepth() == 1);

	const Vec2		uBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(),	coordX, prec.coordBits.x(), prec.uvwBits.x());

	const int		minI			= deFloorFloatToInt32(uBounds.x());
	const int		maxI			= deFloorFloatToInt32(uBounds.y());

	for (int i = minI; i <= maxI; i++)
	{
		const int					x		= wrap(sampler.wrapS, i, level.getWidth());
		const Vector<ScalarType, 4>	color	= lookup<ScalarType>(level, sampler, x, coordY, 0);

		if (isColorValid(prec, color, result))
			return true;
	}

	return false;
}

template<typename PrecType, typename ScalarType>
static bool isNearestSampleResultValid (const ConstPixelBufferAccess&		level,
										const Sampler&						sampler,
										const PrecType&						prec,
										const Vec2&							coord,
										const int							coordZ,
										const Vector<ScalarType, 4>&		result)
{
	const Vec2		uBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(),	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2		vBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getHeight(),	coord.y(), prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinates - without wrap mode
	const int		minI			= deFloorFloatToInt32(uBounds.x());
	const int		maxI			= deFloorFloatToInt32(uBounds.y());
	const int		minJ			= deFloorFloatToInt32(vBounds.x());
	const int		maxJ			= deFloorFloatToInt32(vBounds.y());

	// \todo [2013-07-03 pyry] This could be optimized by first computing ranges based on wrap mode.

	for (int j = minJ; j <= maxJ; j++)
	{
		for (int i = minI; i <= maxI; i++)
		{
			const int					x		= wrap(sampler.wrapS, i, level.getWidth());
			const int					y		= wrap(sampler.wrapT, j, level.getHeight());
			const Vector<ScalarType, 4>	color	= lookup<ScalarType>(level, sampler, x, y, coordZ);

			if (isColorValid(prec, color, result))
				return true;
		}
	}

	return false;
}

template<typename PrecType, typename ScalarType>
static bool isNearestSampleResultValid (const ConstPixelBufferAccess&		level,
										const Sampler&						sampler,
										const PrecType&						prec,
										const Vec3&							coord,
										const Vector<ScalarType, 4>&		result)
{
	const Vec2		uBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(),	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2		vBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getHeight(),	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
	const Vec2		wBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getDepth(),	coord.z(), prec.coordBits.z(), prec.uvwBits.z());

	// Integer coordinates - without wrap mode
	const int		minI			= deFloorFloatToInt32(uBounds.x());
	const int		maxI			= deFloorFloatToInt32(uBounds.y());
	const int		minJ			= deFloorFloatToInt32(vBounds.x());
	const int		maxJ			= deFloorFloatToInt32(vBounds.y());
	const int		minK			= deFloorFloatToInt32(wBounds.x());
	const int		maxK			= deFloorFloatToInt32(wBounds.y());

	// \todo [2013-07-03 pyry] This could be optimized by first computing ranges based on wrap mode.

	for (int k = minK; k <= maxK; k++)
	{
		for (int j = minJ; j <= maxJ; j++)
		{
			for (int i = minI; i <= maxI; i++)
			{
				const int					x		= wrap(sampler.wrapS, i, level.getWidth());
				const int					y		= wrap(sampler.wrapT, j, level.getHeight());
				const int					z		= wrap(sampler.wrapR, k, level.getDepth());
				const Vector<ScalarType, 4>	color	= lookup<ScalarType>(level, sampler, x, y, z);

				if (isColorValid(prec, color, result))
					return true;
			}
		}
	}

	return false;
}

bool isLinearSampleResultValid (const ConstPixelBufferAccess&		level,
								const Sampler&						sampler,
								const LookupPrecision&				prec,
								const float							coordX,
								const int							coordY,
								const Vec4&							result)
{
	const Vec2					uBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(), coordX, prec.coordBits.x(), prec.uvwBits.x());

	const int					minI			= deFloorFloatToInt32(uBounds.x()-0.5f);
	const int					maxI			= deFloorFloatToInt32(uBounds.y()-0.5f);

	const int					w				= level.getWidth();

	const TextureFormat			format			= level.getFormat();
	const TextureChannelClass	texClass		= getTextureChannelClass(format.type);

	DE_ASSERT(texClass				== TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_FLOATING_POINT		||
			  sampler.reductionMode != Sampler::WEIGHTED_AVERAGE);

	DE_UNREF(texClass);
	DE_UNREF(format);

	for (int i = minI; i <= maxI; i++)
	{
		// Wrapped coordinates
		const int	x0		= wrap(sampler.wrapS, i  , w);
		const int	x1		= wrap(sampler.wrapS, i+1, w);

		// Bounds for filtering factors
		const float	minA	= de::clamp((uBounds.x()-0.5f)-float(i), 0.0f, 1.0f);
		const float	maxA	= de::clamp((uBounds.y()-0.5f)-float(i), 0.0f, 1.0f);

		const Vec4	colorA	= lookup<float>(level, sampler, x0, coordY, 0);
		const Vec4	colorB	= lookup<float>(level, sampler, x1, coordY, 0);

		if (sampler.reductionMode == Sampler::WEIGHTED_AVERAGE)
		{
			if (isLinearRangeValid(prec, colorA, colorB, Vec2(minA, maxA), result))
				return true;
		}
		else
		{
			if (isReductionValid(prec, colorA, colorB, sampler.reductionMode, result))
				return true;
		}
	}

	return false;
}

bool isLinearSampleResultValid (const ConstPixelBufferAccess&		level,
								const Sampler&						sampler,
								const LookupPrecision&				prec,
								const Vec2&							coord,
								const int							coordZ,
								const Vec4&							result)
{
	const Vec2					uBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(),	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2					vBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getHeight(),	coord.y(), prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinate bounds for (x0,y0) - without wrap mode
	const int					minI			= deFloorFloatToInt32(uBounds.x()-0.5f);
	const int					maxI			= deFloorFloatToInt32(uBounds.y()-0.5f);
	const int					minJ			= deFloorFloatToInt32(vBounds.x()-0.5f);
	const int					maxJ			= deFloorFloatToInt32(vBounds.y()-0.5f);

	const int					w				= level.getWidth();
	const int					h				= level.getHeight();

	const TextureChannelClass	texClass		= getTextureChannelClass(level.getFormat().type);
	float						searchStep		= texClass == TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	? computeBilinearSearchStepForUnorm(prec) :
												  texClass == TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	? computeBilinearSearchStepForSnorm(prec) :
												  0.0f; // Step is computed for floating-point quads based on texel values.

	DE_ASSERT(texClass				== TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_FLOATING_POINT		||
			  sampler.reductionMode != Sampler::WEIGHTED_AVERAGE);

	// \todo [2013-07-03 pyry] This could be optimized by first computing ranges based on wrap mode.

	for (int j = minJ; j <= maxJ; j++)
	{
		for (int i = minI; i <= maxI; i++)
		{
			// Wrapped coordinates
			const int	x0		= wrap(sampler.wrapS, i  , w);
			const int	x1		= wrap(sampler.wrapS, i+1, w);
			const int	y0		= wrap(sampler.wrapT, j  , h);
			const int	y1		= wrap(sampler.wrapT, j+1, h);

			// Bounds for filtering factors
			const float	minA	= de::clamp((uBounds.x()-0.5f)-float(i), 0.0f, 1.0f);
			const float	maxA	= de::clamp((uBounds.y()-0.5f)-float(i), 0.0f, 1.0f);
			const float	minB	= de::clamp((vBounds.x()-0.5f)-float(j), 0.0f, 1.0f);
			const float	maxB	= de::clamp((vBounds.y()-0.5f)-float(j), 0.0f, 1.0f);

			ColorQuad quad;
			lookupQuad(quad, level, sampler, x0, x1, y0, y1, coordZ);

			if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
				searchStep = computeBilinearSearchStepFromFloatQuad(prec, quad);

			if (sampler.reductionMode == Sampler::WEIGHTED_AVERAGE)
			{
				if (isBilinearRangeValid(prec, quad, Vec2(minA, maxA), Vec2(minB, maxB), searchStep, result))
					return true;
			}
			else
			{
				if (isReductionValid(prec, quad, sampler.reductionMode, result))
					return true;
			}
		}
	}

	return false;
}

static bool isLinearSampleResultValid (const ConstPixelBufferAccess&		level,
									   const Sampler&						sampler,
									   const LookupPrecision&				prec,
									   const Vec3&							coord,
									   const Vec4&							result)
{
	const Vec2					uBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(),	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2					vBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getHeight(),	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
	const Vec2					wBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getDepth(),	coord.z(), prec.coordBits.z(), prec.uvwBits.z());

	// Integer coordinate bounds for (x0,y0) - without wrap mode
	const int					minI			= deFloorFloatToInt32(uBounds.x()-0.5f);
	const int					maxI			= deFloorFloatToInt32(uBounds.y()-0.5f);
	const int					minJ			= deFloorFloatToInt32(vBounds.x()-0.5f);
	const int					maxJ			= deFloorFloatToInt32(vBounds.y()-0.5f);
	const int					minK			= deFloorFloatToInt32(wBounds.x()-0.5f);
	const int					maxK			= deFloorFloatToInt32(wBounds.y()-0.5f);

	const int					w				= level.getWidth();
	const int					h				= level.getHeight();
	const int					d				= level.getDepth();

	const TextureChannelClass	texClass		= getTextureChannelClass(level.getFormat().type);
	float						searchStep		= texClass == TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	? computeBilinearSearchStepForUnorm(prec) :
												  texClass == TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	? computeBilinearSearchStepForSnorm(prec) :
												  0.0f; // Step is computed for floating-point quads based on texel values.

	DE_ASSERT(texClass				== TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_FLOATING_POINT		||
			  sampler.reductionMode	!= Sampler::WEIGHTED_AVERAGE);

	// \todo [2013-07-03 pyry] This could be optimized by first computing ranges based on wrap mode.

	for (int k = minK; k <= maxK; k++)
	{
		for (int j = minJ; j <= maxJ; j++)
		{
			for (int i = minI; i <= maxI; i++)
			{
				// Wrapped coordinates
				const int	x0		= wrap(sampler.wrapS, i  , w);
				const int	x1		= wrap(sampler.wrapS, i+1, w);
				const int	y0		= wrap(sampler.wrapT, j  , h);
				const int	y1		= wrap(sampler.wrapT, j+1, h);
				const int	z0		= wrap(sampler.wrapR, k  , d);
				const int	z1		= wrap(sampler.wrapR, k+1, d);

				// Bounds for filtering factors
				const float	minA	= de::clamp((uBounds.x()-0.5f)-float(i), 0.0f, 1.0f);
				const float	maxA	= de::clamp((uBounds.y()-0.5f)-float(i), 0.0f, 1.0f);
				const float	minB	= de::clamp((vBounds.x()-0.5f)-float(j), 0.0f, 1.0f);
				const float	maxB	= de::clamp((vBounds.y()-0.5f)-float(j), 0.0f, 1.0f);
				const float	minC	= de::clamp((wBounds.x()-0.5f)-float(k), 0.0f, 1.0f);
				const float	maxC	= de::clamp((wBounds.y()-0.5f)-float(k), 0.0f, 1.0f);

				ColorQuad quad0, quad1;
				lookupQuad(quad0, level, sampler, x0, x1, y0, y1, z0);
				lookupQuad(quad1, level, sampler, x0, x1, y0, y1, z1);

				if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
					searchStep = de::min(computeBilinearSearchStepFromFloatQuad(prec, quad0), computeBilinearSearchStepFromFloatQuad(prec, quad1));

				if (sampler.reductionMode == Sampler::WEIGHTED_AVERAGE)
				{
					if (isTrilinearRangeValid(prec, quad0, quad1, Vec2(minA, maxA), Vec2(minB, maxB), Vec2(minC, maxC), searchStep, result))
						return true;
				}
				else
				{
					if (isReductionValid(prec, quad0, quad1, sampler.reductionMode, result))
						return true;
				}
			}
		}
	}

	return false;
}

static bool isNearestMipmapLinearSampleResultValid (const ConstPixelBufferAccess&	level0,
													const ConstPixelBufferAccess&	level1,
													const Sampler&					sampler,
													const LookupPrecision&			prec,
													const float						coord,
													const int						coordY,
													const Vec2&						fBounds,
													const Vec4&						result)
{
	const int		w0				= level0.getWidth();
	const int		w1				= level1.getWidth();

	const Vec2		uBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w0,	coord, prec.coordBits.x(), prec.uvwBits.x());
	const Vec2		uBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w1,	coord, prec.coordBits.x(), prec.uvwBits.x());

	// Integer coordinates - without wrap mode
	const int		minI0			= deFloorFloatToInt32(uBounds0.x());
	const int		maxI0			= deFloorFloatToInt32(uBounds0.y());
	const int		minI1			= deFloorFloatToInt32(uBounds1.x());
	const int		maxI1			= deFloorFloatToInt32(uBounds1.y());

	for (int i0 = minI0; i0 <= maxI0; i0++)
	{
		for (int i1 = minI1; i1 <= maxI1; i1++)
		{
			const Vec4	c0	= lookup<float>(level0, sampler, wrap(sampler.wrapS, i0, w0), coordY, 0);
			const Vec4	c1	= lookup<float>(level1, sampler, wrap(sampler.wrapS, i1, w1), coordY, 0);

			if (isLinearRangeValid(prec, c0, c1, fBounds, result))
				return true;
		}
	}

	return false;
}

static bool isNearestMipmapLinearSampleResultValid (const ConstPixelBufferAccess&	level0,
													const ConstPixelBufferAccess&	level1,
													const Sampler&					sampler,
													const LookupPrecision&			prec,
													const Vec2&						coord,
													const int						coordZ,
													const Vec2&						fBounds,
													const Vec4&						result)
{
	const int		w0				= level0.getWidth();
	const int		w1				= level1.getWidth();
	const int		h0				= level0.getHeight();
	const int		h1				= level1.getHeight();

	const Vec2		uBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w0,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2		uBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w1,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2		vBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h0,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
	const Vec2		vBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h1,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinates - without wrap mode
	const int		minI0			= deFloorFloatToInt32(uBounds0.x());
	const int		maxI0			= deFloorFloatToInt32(uBounds0.y());
	const int		minI1			= deFloorFloatToInt32(uBounds1.x());
	const int		maxI1			= deFloorFloatToInt32(uBounds1.y());
	const int		minJ0			= deFloorFloatToInt32(vBounds0.x());
	const int		maxJ0			= deFloorFloatToInt32(vBounds0.y());
	const int		minJ1			= deFloorFloatToInt32(vBounds1.x());
	const int		maxJ1			= deFloorFloatToInt32(vBounds1.y());

	for (int j0 = minJ0; j0 <= maxJ0; j0++)
	{
		for (int i0 = minI0; i0 <= maxI0; i0++)
		{
			for (int j1 = minJ1; j1 <= maxJ1; j1++)
			{
				for (int i1 = minI1; i1 <= maxI1; i1++)
				{
					const Vec4	c0	= lookup<float>(level0, sampler, wrap(sampler.wrapS, i0, w0), wrap(sampler.wrapT, j0, h0), coordZ);
					const Vec4	c1	= lookup<float>(level1, sampler, wrap(sampler.wrapS, i1, w1), wrap(sampler.wrapT, j1, h1), coordZ);

					if (isLinearRangeValid(prec, c0, c1, fBounds, result))
						return true;
				}
			}
		}
	}

	return false;
}

static bool isNearestMipmapLinearSampleResultValid (const ConstPixelBufferAccess&	level0,
													const ConstPixelBufferAccess&	level1,
													const Sampler&					sampler,
													const LookupPrecision&			prec,
													const Vec3&						coord,
													const Vec2&						fBounds,
													const Vec4&						result)
{
	const int		w0				= level0.getWidth();
	const int		w1				= level1.getWidth();
	const int		h0				= level0.getHeight();
	const int		h1				= level1.getHeight();
	const int		d0				= level0.getDepth();
	const int		d1				= level1.getDepth();

	const Vec2		uBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w0,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2		uBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w1,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2		vBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h0,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
	const Vec2		vBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h1,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
	const Vec2		wBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, d0,	coord.z(), prec.coordBits.z(), prec.uvwBits.z());
	const Vec2		wBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, d1,	coord.z(), prec.coordBits.z(), prec.uvwBits.z());

	// Integer coordinates - without wrap mode
	const int		minI0			= deFloorFloatToInt32(uBounds0.x());
	const int		maxI0			= deFloorFloatToInt32(uBounds0.y());
	const int		minI1			= deFloorFloatToInt32(uBounds1.x());
	const int		maxI1			= deFloorFloatToInt32(uBounds1.y());
	const int		minJ0			= deFloorFloatToInt32(vBounds0.x());
	const int		maxJ0			= deFloorFloatToInt32(vBounds0.y());
	const int		minJ1			= deFloorFloatToInt32(vBounds1.x());
	const int		maxJ1			= deFloorFloatToInt32(vBounds1.y());
	const int		minK0			= deFloorFloatToInt32(wBounds0.x());
	const int		maxK0			= deFloorFloatToInt32(wBounds0.y());
	const int		minK1			= deFloorFloatToInt32(wBounds1.x());
	const int		maxK1			= deFloorFloatToInt32(wBounds1.y());

	for (int k0 = minK0; k0 <= maxK0; k0++)
	{
		for (int j0 = minJ0; j0 <= maxJ0; j0++)
		{
			for (int i0 = minI0; i0 <= maxI0; i0++)
			{
				for (int k1 = minK1; k1 <= maxK1; k1++)
				{
					for (int j1 = minJ1; j1 <= maxJ1; j1++)
					{
						for (int i1 = minI1; i1 <= maxI1; i1++)
						{
							const Vec4	c0	= lookup<float>(level0, sampler, wrap(sampler.wrapS, i0, w0), wrap(sampler.wrapT, j0, h0), wrap(sampler.wrapR, k0, d0));
							const Vec4	c1	= lookup<float>(level1, sampler, wrap(sampler.wrapS, i1, w1), wrap(sampler.wrapT, j1, h1), wrap(sampler.wrapR, k1, d1));

							if (isLinearRangeValid(prec, c0, c1, fBounds, result))
								return true;
						}
					}
				}
			}
		}
	}

	return false;
}

static bool isLinearMipmapLinearSampleResultValid (const ConstPixelBufferAccess&	level0,
												   const ConstPixelBufferAccess&	level1,
												   const Sampler&					sampler,
												   const LookupPrecision&			prec,
												   const float						coordX,
												   const int						coordY,
												   const Vec2&						fBounds,
												   const Vec4&						result)
{
	// \todo [2013-07-04 pyry] This is strictly not correct as coordinates between levels should be dependent.
	//						   Right now this allows pairing any two valid bilinear quads.

	const int					w0				= level0.getWidth();
	const int					w1				= level1.getWidth();

	const Vec2					uBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w0, coordX, prec.coordBits.x(), prec.uvwBits.x());
	const Vec2					uBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w1, coordX, prec.coordBits.x(), prec.uvwBits.x());

	// Integer coordinates - without wrap mode
	const int					minI0			= deFloorFloatToInt32(uBounds0.x()-0.5f);
	const int					maxI0			= deFloorFloatToInt32(uBounds0.y()-0.5f);
	const int					minI1			= deFloorFloatToInt32(uBounds1.x()-0.5f);
	const int					maxI1			= deFloorFloatToInt32(uBounds1.y()-0.5f);

	const TextureChannelClass	texClass		= getTextureChannelClass(level0.getFormat().type);
	const float					cSearchStep		= texClass == TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	? computeBilinearSearchStepForUnorm(prec) :
												  texClass == TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	? computeBilinearSearchStepForSnorm(prec) :
												  0.0f; // Step is computed for floating-point quads based on texel values.

	DE_ASSERT(texClass				== TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_FLOATING_POINT		||
			  sampler.reductionMode	!= Sampler::WEIGHTED_AVERAGE);

	for (int i0 = minI0; i0 <= maxI0; i0++)
	{
		ColorLine	line0;
		float		searchStep0;

		{
			const int	x0		= wrap(sampler.wrapS, i0  , w0);
			const int	x1		= wrap(sampler.wrapS, i0+1, w0);
			lookupLine(line0, level0, sampler, x0, x1, coordY);

			if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
				searchStep0 = computeBilinearSearchStepFromFloatLine(prec, line0);
			else
				searchStep0 = cSearchStep;
		}

		const float	minA0	= de::clamp((uBounds0.x()-0.5f)-float(i0), 0.0f, 1.0f);
		const float	maxA0	= de::clamp((uBounds0.y()-0.5f)-float(i0), 0.0f, 1.0f);

		for (int i1 = minI1; i1 <= maxI1; i1++)
		{
			ColorLine	line1;
			float		searchStep1;

			{
				const int	x0		= wrap(sampler.wrapS, i1  , w1);
				const int	x1		= wrap(sampler.wrapS, i1+1, w1);
				lookupLine(line1, level1, sampler, x0, x1, coordY);

				if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
					searchStep1 = computeBilinearSearchStepFromFloatLine(prec, line1);
				else
					searchStep1 = cSearchStep;
			}

			const float	minA1	= de::clamp((uBounds1.x()-0.5f)-float(i1), 0.0f, 1.0f);
			const float	maxA1	= de::clamp((uBounds1.y()-0.5f)-float(i1), 0.0f, 1.0f);

			if (is1DTrilinearFilterResultValid(prec, line0, line1, Vec2(minA0, maxA0), Vec2(minA1, maxA1), fBounds, de::min(searchStep0, searchStep1), result))
				return true;
		}
	}

	return false;
}

static bool isLinearMipmapLinearSampleResultValid (const ConstPixelBufferAccess&	level0,
												   const ConstPixelBufferAccess&	level1,
												   const Sampler&					sampler,
												   const LookupPrecision&			prec,
												   const Vec2&						coord,
												   const int						coordZ,
												   const Vec2&						fBounds,
												   const Vec4&						result)
{
	// \todo [2013-07-04 pyry] This is strictly not correct as coordinates between levels should be dependent.
	//						   Right now this allows pairing any two valid bilinear quads.

	const int					w0				= level0.getWidth();
	const int					w1				= level1.getWidth();
	const int					h0				= level0.getHeight();
	const int					h1				= level1.getHeight();

	const Vec2					uBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w0,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2					uBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w1,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2					vBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h0,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
	const Vec2					vBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h1,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinates - without wrap mode
	const int					minI0			= deFloorFloatToInt32(uBounds0.x()-0.5f);
	const int					maxI0			= deFloorFloatToInt32(uBounds0.y()-0.5f);
	const int					minI1			= deFloorFloatToInt32(uBounds1.x()-0.5f);
	const int					maxI1			= deFloorFloatToInt32(uBounds1.y()-0.5f);
	const int					minJ0			= deFloorFloatToInt32(vBounds0.x()-0.5f);
	const int					maxJ0			= deFloorFloatToInt32(vBounds0.y()-0.5f);
	const int					minJ1			= deFloorFloatToInt32(vBounds1.x()-0.5f);
	const int					maxJ1			= deFloorFloatToInt32(vBounds1.y()-0.5f);

	const TextureChannelClass	texClass		= getTextureChannelClass(level0.getFormat().type);
	const float					cSearchStep		= texClass == TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	? computeBilinearSearchStepForUnorm(prec) :
												  texClass == TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	? computeBilinearSearchStepForSnorm(prec) :
												  0.0f; // Step is computed for floating-point quads based on texel values.

	DE_ASSERT(texClass				== TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_FLOATING_POINT		||
			  sampler.reductionMode	!= Sampler::WEIGHTED_AVERAGE);

	for (int j0 = minJ0; j0 <= maxJ0; j0++)
	{
		for (int i0 = minI0; i0 <= maxI0; i0++)
		{
			ColorQuad	quad0;
			float		searchStep0;

			{
				const int	x0		= wrap(sampler.wrapS, i0  , w0);
				const int	x1		= wrap(sampler.wrapS, i0+1, w0);
				const int	y0		= wrap(sampler.wrapT, j0  , h0);
				const int	y1		= wrap(sampler.wrapT, j0+1, h0);
				lookupQuad(quad0, level0, sampler, x0, x1, y0, y1, coordZ);

				if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
					searchStep0 = computeBilinearSearchStepFromFloatQuad(prec, quad0);
				else
					searchStep0 = cSearchStep;
			}

			const float	minA0	= de::clamp((uBounds0.x()-0.5f)-float(i0), 0.0f, 1.0f);
			const float	maxA0	= de::clamp((uBounds0.y()-0.5f)-float(i0), 0.0f, 1.0f);
			const float	minB0	= de::clamp((vBounds0.x()-0.5f)-float(j0), 0.0f, 1.0f);
			const float	maxB0	= de::clamp((vBounds0.y()-0.5f)-float(j0), 0.0f, 1.0f);

			for (int j1 = minJ1; j1 <= maxJ1; j1++)
			{
				for (int i1 = minI1; i1 <= maxI1; i1++)
				{
					ColorQuad	quad1;
					float		searchStep1;

					{
						const int	x0		= wrap(sampler.wrapS, i1  , w1);
						const int	x1		= wrap(sampler.wrapS, i1+1, w1);
						const int	y0		= wrap(sampler.wrapT, j1  , h1);
						const int	y1		= wrap(sampler.wrapT, j1+1, h1);
						lookupQuad(quad1, level1, sampler, x0, x1, y0, y1, coordZ);

						if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
							searchStep1 = computeBilinearSearchStepFromFloatQuad(prec, quad1);
						else
							searchStep1 = cSearchStep;
					}

					const float	minA1	= de::clamp((uBounds1.x()-0.5f)-float(i1), 0.0f, 1.0f);
					const float	maxA1	= de::clamp((uBounds1.y()-0.5f)-float(i1), 0.0f, 1.0f);
					const float	minB1	= de::clamp((vBounds1.x()-0.5f)-float(j1), 0.0f, 1.0f);
					const float	maxB1	= de::clamp((vBounds1.y()-0.5f)-float(j1), 0.0f, 1.0f);

					if (is2DTrilinearFilterResultValid(prec, quad0, quad1, Vec2(minA0, maxA0), Vec2(minB0, maxB0), Vec2(minA1, maxA1), Vec2(minB1, maxB1),
													   fBounds, de::min(searchStep0, searchStep1), result))
						return true;
				}
			}
		}
	}

	return false;
}

static bool isLinearMipmapLinearSampleResultValid (const ConstPixelBufferAccess&	level0,
												   const ConstPixelBufferAccess&	level1,
												   const Sampler&					sampler,
												   const LookupPrecision&			prec,
												   const Vec3&						coord,
												   const Vec2&						fBounds,
												   const Vec4&						result)
{
	// \todo [2013-07-04 pyry] This is strictly not correct as coordinates between levels should be dependent.
	//						   Right now this allows pairing any two valid bilinear quads.

	const int					w0				= level0.getWidth();
	const int					w1				= level1.getWidth();
	const int					h0				= level0.getHeight();
	const int					h1				= level1.getHeight();
	const int					d0				= level0.getDepth();
	const int					d1				= level1.getDepth();

	const Vec2					uBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w0,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2					uBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w1,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2					vBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h0,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
	const Vec2					vBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h1,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
	const Vec2					wBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, d0,	coord.z(), prec.coordBits.z(), prec.uvwBits.z());
	const Vec2					wBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, d1,	coord.z(), prec.coordBits.z(), prec.uvwBits.z());

	// Integer coordinates - without wrap mode
	const int					minI0			= deFloorFloatToInt32(uBounds0.x()-0.5f);
	const int					maxI0			= deFloorFloatToInt32(uBounds0.y()-0.5f);
	const int					minI1			= deFloorFloatToInt32(uBounds1.x()-0.5f);
	const int					maxI1			= deFloorFloatToInt32(uBounds1.y()-0.5f);
	const int					minJ0			= deFloorFloatToInt32(vBounds0.x()-0.5f);
	const int					maxJ0			= deFloorFloatToInt32(vBounds0.y()-0.5f);
	const int					minJ1			= deFloorFloatToInt32(vBounds1.x()-0.5f);
	const int					maxJ1			= deFloorFloatToInt32(vBounds1.y()-0.5f);
	const int					minK0			= deFloorFloatToInt32(wBounds0.x()-0.5f);
	const int					maxK0			= deFloorFloatToInt32(wBounds0.y()-0.5f);
	const int					minK1			= deFloorFloatToInt32(wBounds1.x()-0.5f);
	const int					maxK1			= deFloorFloatToInt32(wBounds1.y()-0.5f);

	const TextureChannelClass	texClass		= getTextureChannelClass(level0.getFormat().type);
	const float					cSearchStep		= texClass == TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	? computeBilinearSearchStepForUnorm(prec) :
												  texClass == TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	? computeBilinearSearchStepForSnorm(prec) :
												  0.0f; // Step is computed for floating-point quads based on texel values.

	DE_ASSERT(texClass				== TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_FLOATING_POINT		||
			  sampler.reductionMode	!= Sampler::WEIGHTED_AVERAGE);

	for (int k0 = minK0; k0 <= maxK0; k0++)
	{
		for (int j0 = minJ0; j0 <= maxJ0; j0++)
		{
			for (int i0 = minI0; i0 <= maxI0; i0++)
			{
				ColorQuad	quad00, quad01;
				float		searchStep0;

				{
					const int	x0		= wrap(sampler.wrapS, i0  , w0);
					const int	x1		= wrap(sampler.wrapS, i0+1, w0);
					const int	y0		= wrap(sampler.wrapT, j0  , h0);
					const int	y1		= wrap(sampler.wrapT, j0+1, h0);
					const int	z0		= wrap(sampler.wrapR, k0  , d0);
					const int	z1		= wrap(sampler.wrapR, k0+1, d0);
					lookupQuad(quad00, level0, sampler, x0, x1, y0, y1, z0);
					lookupQuad(quad01, level0, sampler, x0, x1, y0, y1, z1);

					if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
						searchStep0 = de::min(computeBilinearSearchStepFromFloatQuad(prec, quad00), computeBilinearSearchStepFromFloatQuad(prec, quad01));
					else
						searchStep0 = cSearchStep;
				}

				const float	minA0	= de::clamp((uBounds0.x()-0.5f)-float(i0), 0.0f, 1.0f);
				const float	maxA0	= de::clamp((uBounds0.y()-0.5f)-float(i0), 0.0f, 1.0f);
				const float	minB0	= de::clamp((vBounds0.x()-0.5f)-float(j0), 0.0f, 1.0f);
				const float	maxB0	= de::clamp((vBounds0.y()-0.5f)-float(j0), 0.0f, 1.0f);
				const float	minC0	= de::clamp((wBounds0.x()-0.5f)-float(k0), 0.0f, 1.0f);
				const float	maxC0	= de::clamp((wBounds0.y()-0.5f)-float(k0), 0.0f, 1.0f);

				for (int k1 = minK1; k1 <= maxK1; k1++)
				{
					for (int j1 = minJ1; j1 <= maxJ1; j1++)
					{
						for (int i1 = minI1; i1 <= maxI1; i1++)
						{
							ColorQuad	quad10, quad11;
							float		searchStep1;

							{
								const int	x0		= wrap(sampler.wrapS, i1  , w1);
								const int	x1		= wrap(sampler.wrapS, i1+1, w1);
								const int	y0		= wrap(sampler.wrapT, j1  , h1);
								const int	y1		= wrap(sampler.wrapT, j1+1, h1);
								const int	z0		= wrap(sampler.wrapR, k1  , d1);
								const int	z1		= wrap(sampler.wrapR, k1+1, d1);
								lookupQuad(quad10, level1, sampler, x0, x1, y0, y1, z0);
								lookupQuad(quad11, level1, sampler, x0, x1, y0, y1, z1);

								if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
									searchStep1 = de::min(computeBilinearSearchStepFromFloatQuad(prec, quad10), computeBilinearSearchStepFromFloatQuad(prec, quad11));
								else
									searchStep1 = cSearchStep;
							}

							const float	minA1	= de::clamp((uBounds1.x()-0.5f)-float(i1), 0.0f, 1.0f);
							const float	maxA1	= de::clamp((uBounds1.y()-0.5f)-float(i1), 0.0f, 1.0f);
							const float	minB1	= de::clamp((vBounds1.x()-0.5f)-float(j1), 0.0f, 1.0f);
							const float	maxB1	= de::clamp((vBounds1.y()-0.5f)-float(j1), 0.0f, 1.0f);
							const float	minC1	= de::clamp((wBounds1.x()-0.5f)-float(k1), 0.0f, 1.0f);
							const float	maxC1	= de::clamp((wBounds1.y()-0.5f)-float(k1), 0.0f, 1.0f);

							if (is3DTrilinearFilterResultValid(prec, quad00, quad01, quad10, quad11,
															   Vec2(minA0, maxA0), Vec2(minB0, maxB0), Vec2(minC0, maxC0),
															   Vec2(minA1, maxA1), Vec2(minB1, maxB1), Vec2(minC1, maxC1),
															   fBounds, de::min(searchStep0, searchStep1), result))
								return true;
						}
					}
				}
			}
		}
	}

	return false;
}

static bool isLevelSampleResultValid (const ConstPixelBufferAccess&		level,
									  const Sampler&					sampler,
									  const Sampler::FilterMode			filterMode,
									  const LookupPrecision&			prec,
									  const float						coordX,
									  const int							coordY,
									  const Vec4&						result)
{
	if (filterMode == Sampler::LINEAR)
		return isLinearSampleResultValid(level, sampler, prec, coordX, coordY, result);
	else
		return isNearestSampleResultValid(level, sampler, prec, coordX, coordY, result);
}

static bool isLevelSampleResultValid (const ConstPixelBufferAccess&		level,
									  const Sampler&					sampler,
									  const Sampler::FilterMode			filterMode,
									  const LookupPrecision&			prec,
									  const Vec2&						coord,
									  const int							coordZ,
									  const Vec4&						result)
{
	if (filterMode == Sampler::LINEAR)
		return isLinearSampleResultValid(level, sampler, prec, coord, coordZ, result);
	else
		return isNearestSampleResultValid(level, sampler, prec, coord, coordZ, result);
}

static bool isMipmapLinearSampleResultValid (const ConstPixelBufferAccess&		level0,
										     const ConstPixelBufferAccess&		level1,
											 const Sampler&						sampler,
										     const Sampler::FilterMode			levelFilter,
										     const LookupPrecision&				prec,
										     const float						coordX,
											 const int							coordY,
										     const Vec2&						fBounds,
										     const Vec4&						result)
{
	if (levelFilter == Sampler::LINEAR)
		return isLinearMipmapLinearSampleResultValid(level0, level1, sampler, prec, coordX, coordY, fBounds, result);
	else
		return isNearestMipmapLinearSampleResultValid(level0, level1, sampler, prec, coordX, coordY, fBounds, result);
}

static bool isMipmapLinearSampleResultValid (const ConstPixelBufferAccess&		level0,
										     const ConstPixelBufferAccess&		level1,
											 const Sampler&						sampler,
										     const Sampler::FilterMode			levelFilter,
										     const LookupPrecision&				prec,
										     const Vec2&						coord,
											 const int							coordZ,
										     const Vec2&						fBounds,
										     const Vec4&						result)
{
	if (levelFilter == Sampler::LINEAR)
		return isLinearMipmapLinearSampleResultValid(level0, level1, sampler, prec, coord, coordZ, fBounds, result);
	else
		return isNearestMipmapLinearSampleResultValid(level0, level1, sampler, prec, coord, coordZ, fBounds, result);
}

bool isLookupResultValid (const Texture2DView& texture, const Sampler& sampler, const LookupPrecision& prec, const Vec2& coord, const Vec2& lodBounds, const Vec4& result)
{
	const float		minLod			= lodBounds.x();
	const float		maxLod			= lodBounds.y();
	const bool		canBeMagnified	= minLod <= sampler.lodThreshold;
	const bool		canBeMinified	= maxLod > sampler.lodThreshold;

	DE_ASSERT(isSamplerSupported(sampler));

	if (canBeMagnified)
	{
		if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.magFilter, prec, coord, 0, result))
			return true;
	}

	if (canBeMinified)
	{
		const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
		const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
		const int	minTexLevel		= 0;
		const int	maxTexLevel		= texture.getNumLevels()-1;

		DE_ASSERT(minTexLevel <= maxTexLevel);

		if (isLinearMipmap && minTexLevel < maxTexLevel)
		{
			const int		minLevel		= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
			const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);

			DE_ASSERT(minLevel <= maxLevel);

			for (int level = minLevel; level <= maxLevel; level++)
			{
				const float		minF	= de::clamp(minLod - float(level), 0.0f, 1.0f);
				const float		maxF	= de::clamp(maxLod - float(level), 0.0f, 1.0f);

				if (isMipmapLinearSampleResultValid(texture.getLevel(level), texture.getLevel(level+1), sampler, getLevelFilter(sampler.minFilter), prec, coord, 0, Vec2(minF, maxF), result))
					return true;
			}
		}
		else if (isNearestMipmap)
		{
			// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
			//		 decision to allow floor(lod + 0.5) as well.
			const int		minLevel		= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
			const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);

			DE_ASSERT(minLevel <= maxLevel);

			for (int level = minLevel; level <= maxLevel; level++)
			{
				if (isLevelSampleResultValid(texture.getLevel(level), sampler, getLevelFilter(sampler.minFilter), prec, coord, 0, result))
					return true;
			}
		}
		else
		{
			if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.minFilter, prec, coord, 0, result))
				return true;
		}
	}

	return false;
}

bool isLookupResultValid (const Texture1DView& texture, const Sampler& sampler, const LookupPrecision& prec, const float coord, const Vec2& lodBounds, const Vec4& result)
{
	const float		minLod			= lodBounds.x();
	const float		maxLod			= lodBounds.y();
	const bool		canBeMagnified	= minLod <= sampler.lodThreshold;
	const bool		canBeMinified	= maxLod > sampler.lodThreshold;

	DE_ASSERT(isSamplerSupported(sampler));

	if (canBeMagnified)
	{
		if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.magFilter, prec, coord, 0, result))
			return true;
	}

	if (canBeMinified)
	{
		const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
		const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
		const int	minTexLevel		= 0;
		const int	maxTexLevel		= texture.getNumLevels()-1;

		DE_ASSERT(minTexLevel <= maxTexLevel);

		if (isLinearMipmap && minTexLevel < maxTexLevel)
		{
			const int		minLevel		= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
			const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);

			DE_ASSERT(minLevel <= maxLevel);

			for (int level = minLevel; level <= maxLevel; level++)
			{
				const float		minF	= de::clamp(minLod - float(level), 0.0f, 1.0f);
				const float		maxF	= de::clamp(maxLod - float(level), 0.0f, 1.0f);

				if (isMipmapLinearSampleResultValid(texture.getLevel(level), texture.getLevel(level+1), sampler, getLevelFilter(sampler.minFilter), prec, coord, 0, Vec2(minF, maxF), result))
					return true;
			}
		}
		else if (isNearestMipmap)
		{
			// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
			//		 decision to allow floor(lod + 0.5) as well.
			const int		minLevel		= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
			const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);

			DE_ASSERT(minLevel <= maxLevel);

			for (int level = minLevel; level <= maxLevel; level++)
			{
				if (isLevelSampleResultValid(texture.getLevel(level), sampler, getLevelFilter(sampler.minFilter), prec, coord, 0, result))
					return true;
			}
		}
		else
		{
			if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.minFilter, prec, coord, 0, result))
				return true;
		}
	}

	return false;
}

static bool isSeamlessLinearSampleResultValid (const ConstPixelBufferAccess (&faces)[CUBEFACE_LAST],
											   const Sampler&				sampler,
											   const LookupPrecision&		prec,
											   const CubeFaceFloatCoords&	coords,
											   const Vec4&					result)
{
	const int					size			= faces[coords.face].getWidth();

	const Vec2					uBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size, coords.s, prec.coordBits.x(), prec.uvwBits.x());
	const Vec2					vBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size, coords.t, prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinate bounds for (x0,y0) - without wrap mode
	const int					minI			= deFloorFloatToInt32(uBounds.x()-0.5f);
	const int					maxI			= deFloorFloatToInt32(uBounds.y()-0.5f);
	const int					minJ			= deFloorFloatToInt32(vBounds.x()-0.5f);
	const int					maxJ			= deFloorFloatToInt32(vBounds.y()-0.5f);

	const TextureChannelClass	texClass		= getTextureChannelClass(faces[coords.face].getFormat().type);
	float						searchStep		= texClass == TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	? computeBilinearSearchStepForUnorm(prec) :
												  texClass == TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	? computeBilinearSearchStepForSnorm(prec) :
												  0.0f; // Step is computed for floating-point quads based on texel values.

	DE_ASSERT(texClass				== TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_FLOATING_POINT		||
			  sampler.reductionMode != Sampler::WEIGHTED_AVERAGE);

	for (int j = minJ; j <= maxJ; j++)
	{
		for (int i = minI; i <= maxI; i++)
		{
			const CubeFaceIntCoords	c00	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i+0, j+0)), size);
			const CubeFaceIntCoords	c10	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i+1, j+0)), size);
			const CubeFaceIntCoords	c01	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i+0, j+1)), size);
			const CubeFaceIntCoords	c11	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i+1, j+1)), size);

			// If any of samples is out of both edges, implementations can do pretty much anything according to spec.
			// \todo [2013-07-08 pyry] Test the special case where all corner pixels have exactly the same color.
			if (c00.face == CUBEFACE_LAST || c01.face == CUBEFACE_LAST || c10.face == CUBEFACE_LAST || c11.face == CUBEFACE_LAST)
				return true;

			// Bounds for filtering factors
			const float	minA	= de::clamp((uBounds.x()-0.5f)-float(i), 0.0f, 1.0f);
			const float	maxA	= de::clamp((uBounds.y()-0.5f)-float(i), 0.0f, 1.0f);
			const float	minB	= de::clamp((vBounds.x()-0.5f)-float(j), 0.0f, 1.0f);
			const float	maxB	= de::clamp((vBounds.y()-0.5f)-float(j), 0.0f, 1.0f);

			ColorQuad quad;
			quad.p00 = lookup<float>(faces[c00.face], sampler, c00.s, c00.t, 0);
			quad.p10 = lookup<float>(faces[c10.face], sampler, c10.s, c10.t, 0);
			quad.p01 = lookup<float>(faces[c01.face], sampler, c01.s, c01.t, 0);
			quad.p11 = lookup<float>(faces[c11.face], sampler, c11.s, c11.t, 0);

			if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
				searchStep = computeBilinearSearchStepFromFloatQuad(prec, quad);

			if (sampler.reductionMode == Sampler::WEIGHTED_AVERAGE)
			{
				if (isBilinearRangeValid(prec, quad, Vec2(minA, maxA), Vec2(minB, maxB), searchStep, result))
					return true;
			}
			else
			{
				if (isReductionValid(prec, quad, sampler.reductionMode, result))
					return true;
			}
		}
	}

	return false;
}

static bool isSeamplessLinearMipmapLinearSampleResultValid (const ConstPixelBufferAccess	(&faces0)[CUBEFACE_LAST],
															const ConstPixelBufferAccess	(&faces1)[CUBEFACE_LAST],
															const Sampler&					sampler,
															const LookupPrecision&			prec,
															const CubeFaceFloatCoords&		coords,
															const Vec2&						fBounds,
															const Vec4&						result)
{
	// \todo [2013-07-04 pyry] This is strictly not correct as coordinates between levels should be dependent.
	//						   Right now this allows pairing any two valid bilinear quads.

	const int					size0			= faces0[coords.face].getWidth();
	const int					size1			= faces1[coords.face].getWidth();

	const Vec2					uBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size0,	coords.s, prec.coordBits.x(), prec.uvwBits.x());
	const Vec2					uBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size1,	coords.s, prec.coordBits.x(), prec.uvwBits.x());
	const Vec2					vBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size0,	coords.t, prec.coordBits.y(), prec.uvwBits.y());
	const Vec2					vBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size1,	coords.t, prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinates - without wrap mode
	const int					minI0			= deFloorFloatToInt32(uBounds0.x()-0.5f);
	const int					maxI0			= deFloorFloatToInt32(uBounds0.y()-0.5f);
	const int					minI1			= deFloorFloatToInt32(uBounds1.x()-0.5f);
	const int					maxI1			= deFloorFloatToInt32(uBounds1.y()-0.5f);
	const int					minJ0			= deFloorFloatToInt32(vBounds0.x()-0.5f);
	const int					maxJ0			= deFloorFloatToInt32(vBounds0.y()-0.5f);
	const int					minJ1			= deFloorFloatToInt32(vBounds1.x()-0.5f);
	const int					maxJ1			= deFloorFloatToInt32(vBounds1.y()-0.5f);

	const TextureChannelClass	texClass		= getTextureChannelClass(faces0[coords.face].getFormat().type);
	const float					cSearchStep		= texClass == TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	? computeBilinearSearchStepForUnorm(prec) :
												  texClass == TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	? computeBilinearSearchStepForSnorm(prec) :
												  0.0f; // Step is computed for floating-point quads based on texel values.

	DE_ASSERT(texClass				== TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	||
			  texClass				== TEXTURECHANNELCLASS_FLOATING_POINT		||
			  sampler.reductionMode	!= Sampler::WEIGHTED_AVERAGE);

	for (int j0 = minJ0; j0 <= maxJ0; j0++)
	{
		for (int i0 = minI0; i0 <= maxI0; i0++)
		{
			ColorQuad	quad0;
			float		searchStep0;

			{
				const CubeFaceIntCoords	c00	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i0+0, j0+0)), size0);
				const CubeFaceIntCoords	c10	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i0+1, j0+0)), size0);
				const CubeFaceIntCoords	c01	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i0+0, j0+1)), size0);
				const CubeFaceIntCoords	c11	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i0+1, j0+1)), size0);

				// If any of samples is out of both edges, implementations can do pretty much anything according to spec.
				// \todo [2013-07-08 pyry] Test the special case where all corner pixels have exactly the same color.
				if (c00.face == CUBEFACE_LAST || c01.face == CUBEFACE_LAST || c10.face == CUBEFACE_LAST || c11.face == CUBEFACE_LAST)
					return true;

				quad0.p00 = lookup<float>(faces0[c00.face], sampler, c00.s, c00.t, 0);
				quad0.p10 = lookup<float>(faces0[c10.face], sampler, c10.s, c10.t, 0);
				quad0.p01 = lookup<float>(faces0[c01.face], sampler, c01.s, c01.t, 0);
				quad0.p11 = lookup<float>(faces0[c11.face], sampler, c11.s, c11.t, 0);

				if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
					searchStep0 = computeBilinearSearchStepFromFloatQuad(prec, quad0);
				else
					searchStep0 = cSearchStep;
			}

			const float	minA0	= de::clamp((uBounds0.x()-0.5f)-float(i0), 0.0f, 1.0f);
			const float	maxA0	= de::clamp((uBounds0.y()-0.5f)-float(i0), 0.0f, 1.0f);
			const float	minB0	= de::clamp((vBounds0.x()-0.5f)-float(j0), 0.0f, 1.0f);
			const float	maxB0	= de::clamp((vBounds0.y()-0.5f)-float(j0), 0.0f, 1.0f);

			for (int j1 = minJ1; j1 <= maxJ1; j1++)
			{
				for (int i1 = minI1; i1 <= maxI1; i1++)
				{
					ColorQuad	quad1;
					float		searchStep1;

					{
						const CubeFaceIntCoords	c00	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i1+0, j1+0)), size1);
						const CubeFaceIntCoords	c10	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i1+1, j1+0)), size1);
						const CubeFaceIntCoords	c01	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i1+0, j1+1)), size1);
						const CubeFaceIntCoords	c11	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i1+1, j1+1)), size1);

						if (c00.face == CUBEFACE_LAST || c01.face == CUBEFACE_LAST || c10.face == CUBEFACE_LAST || c11.face == CUBEFACE_LAST)
							return true;

						quad1.p00 = lookup<float>(faces1[c00.face], sampler, c00.s, c00.t, 0);
						quad1.p10 = lookup<float>(faces1[c10.face], sampler, c10.s, c10.t, 0);
						quad1.p01 = lookup<float>(faces1[c01.face], sampler, c01.s, c01.t, 0);
						quad1.p11 = lookup<float>(faces1[c11.face], sampler, c11.s, c11.t, 0);

						if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
							searchStep1 = computeBilinearSearchStepFromFloatQuad(prec, quad1);
						else
							searchStep1 = cSearchStep;
					}

					const float	minA1	= de::clamp((uBounds1.x()-0.5f)-float(i1), 0.0f, 1.0f);
					const float	maxA1	= de::clamp((uBounds1.y()-0.5f)-float(i1), 0.0f, 1.0f);
					const float	minB1	= de::clamp((vBounds1.x()-0.5f)-float(j1), 0.0f, 1.0f);
					const float	maxB1	= de::clamp((vBounds1.y()-0.5f)-float(j1), 0.0f, 1.0f);

					if (is2DTrilinearFilterResultValid(prec, quad0, quad1, Vec2(minA0, maxA0), Vec2(minB0, maxB0), Vec2(minA1, maxA1), Vec2(minB1, maxB1),
													   fBounds, de::min(searchStep0, searchStep1), result))
						return true;
				}
			}
		}
	}

	return false;
}

static bool isCubeLevelSampleResultValid (const ConstPixelBufferAccess		(&level)[CUBEFACE_LAST],
										  const Sampler&					sampler,
										  const Sampler::FilterMode			filterMode,
										  const LookupPrecision&			prec,
										  const CubeFaceFloatCoords&		coords,
										  const Vec4&						result)
{
	if (filterMode == Sampler::LINEAR)
	{
		if (sampler.seamlessCubeMap)
			return isSeamlessLinearSampleResultValid(level, sampler, prec, coords, result);
		else
			return isLinearSampleResultValid(level[coords.face], sampler, prec, Vec2(coords.s, coords.t), 0, result);
	}
	else
		return isNearestSampleResultValid(level[coords.face], sampler, prec, Vec2(coords.s, coords.t), 0, result);
}

static bool isCubeMipmapLinearSampleResultValid (const ConstPixelBufferAccess	(&faces0)[CUBEFACE_LAST],
												 const ConstPixelBufferAccess	(&faces1)[CUBEFACE_LAST],
												 const Sampler&					sampler,
												 const Sampler::FilterMode		levelFilter,
												 const LookupPrecision&			prec,
												 const CubeFaceFloatCoords&		coords,
												 const Vec2&					fBounds,
												 const Vec4&					result)
{
	if (levelFilter == Sampler::LINEAR)
	{
		if (sampler.seamlessCubeMap)
			return isSeamplessLinearMipmapLinearSampleResultValid(faces0, faces1, sampler, prec, coords, fBounds, result);
		else
			return isLinearMipmapLinearSampleResultValid(faces0[coords.face], faces1[coords.face], sampler, prec, Vec2(coords.s, coords.t), 0, fBounds, result);
	}
	else
		return isNearestMipmapLinearSampleResultValid(faces0[coords.face], faces1[coords.face], sampler, prec, Vec2(coords.s, coords.t), 0, fBounds, result);
}

static void getCubeLevelFaces (const TextureCubeView& texture, const int levelNdx, ConstPixelBufferAccess (&out)[CUBEFACE_LAST])
{
	for (int faceNdx = 0; faceNdx < CUBEFACE_LAST; faceNdx++)
		out[faceNdx] = texture.getLevelFace(levelNdx, (CubeFace)faceNdx);
}

bool isLookupResultValid (const TextureCubeView& texture, const Sampler& sampler, const LookupPrecision& prec, const Vec3& coord, const Vec2& lodBounds, const Vec4& result)
{
	int			numPossibleFaces				= 0;
	CubeFace	possibleFaces[CUBEFACE_LAST];

	DE_ASSERT(isSamplerSupported(sampler));

	getPossibleCubeFaces(coord, prec.coordBits, &possibleFaces[0], numPossibleFaces);

	if (numPossibleFaces == 0)
		return true; // Result is undefined.

	for (int tryFaceNdx = 0; tryFaceNdx < numPossibleFaces; tryFaceNdx++)
	{
		const CubeFaceFloatCoords	faceCoords		(possibleFaces[tryFaceNdx], projectToFace(possibleFaces[tryFaceNdx], coord));
		const float					minLod			= lodBounds.x();
		const float					maxLod			= lodBounds.y();
		const bool					canBeMagnified	= minLod <= sampler.lodThreshold;
		const bool					canBeMinified	= maxLod > sampler.lodThreshold;

		if (canBeMagnified)
		{
			ConstPixelBufferAccess faces[CUBEFACE_LAST];
			getCubeLevelFaces(texture, 0, faces);

			if (isCubeLevelSampleResultValid(faces, sampler, sampler.magFilter, prec, faceCoords, result))
				return true;
		}

		if (canBeMinified)
		{
			const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
			const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
			const int	minTexLevel		= 0;
			const int	maxTexLevel		= texture.getNumLevels()-1;

			DE_ASSERT(minTexLevel <= maxTexLevel);

			if (isLinearMipmap && minTexLevel < maxTexLevel)
			{
				const int	minLevel	= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
				const int	maxLevel	= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);

				DE_ASSERT(minLevel <= maxLevel);

				for (int levelNdx = minLevel; levelNdx <= maxLevel; levelNdx++)
				{
					const float				minF	= de::clamp(minLod - float(levelNdx), 0.0f, 1.0f);
					const float				maxF	= de::clamp(maxLod - float(levelNdx), 0.0f, 1.0f);

					ConstPixelBufferAccess	faces0[CUBEFACE_LAST];
					ConstPixelBufferAccess	faces1[CUBEFACE_LAST];

					getCubeLevelFaces(texture, levelNdx,		faces0);
					getCubeLevelFaces(texture, levelNdx + 1,	faces1);

					if (isCubeMipmapLinearSampleResultValid(faces0, faces1, sampler, getLevelFilter(sampler.minFilter), prec, faceCoords, Vec2(minF, maxF), result))
						return true;
				}
			}
			else if (isNearestMipmap)
			{
				// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
				//		 decision to allow floor(lod + 0.5) as well.
				const int	minLevel	= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
				const int	maxLevel	= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);

				DE_ASSERT(minLevel <= maxLevel);

				for (int levelNdx = minLevel; levelNdx <= maxLevel; levelNdx++)
				{
					ConstPixelBufferAccess faces[CUBEFACE_LAST];
					getCubeLevelFaces(texture, levelNdx, faces);

					if (isCubeLevelSampleResultValid(faces, sampler, getLevelFilter(sampler.minFilter), prec, faceCoords, result))
						return true;
				}
			}
			else
			{
				ConstPixelBufferAccess faces[CUBEFACE_LAST];
				getCubeLevelFaces(texture, 0, faces);

				if (isCubeLevelSampleResultValid(faces, sampler, sampler.minFilter, prec, faceCoords, result))
					return true;
			}
		}
	}

	return false;
}

static inline IVec2 computeLayerRange (int numLayers, int numCoordBits, float layerCoord)
{
	const float	err		= computeFloatingPointError(layerCoord, numCoordBits);
	const int	minL	= (int)deFloatFloor(layerCoord - err + 0.5f);		// Round down
	const int	maxL	= (int)deFloatCeil(layerCoord + err + 0.5f) - 1;	// Round up

	DE_ASSERT(minL <= maxL);

	return IVec2(de::clamp(minL, 0, numLayers-1), de::clamp(maxL, 0, numLayers-1));
}

bool isLookupResultValid (const Texture1DArrayView& texture, const Sampler& sampler, const LookupPrecision& prec, const Vec2& coord, const Vec2& lodBounds, const Vec4& result)
{
	const IVec2		layerRange		= computeLayerRange(texture.getNumLayers(), prec.coordBits.y(), coord.y());
	const float		coordX			= coord.x();
	const float		minLod			= lodBounds.x();
	const float		maxLod			= lodBounds.y();
	const bool		canBeMagnified	= minLod <= sampler.lodThreshold;
	const bool		canBeMinified	= maxLod > sampler.lodThreshold;

	DE_ASSERT(isSamplerSupported(sampler));

	for (int layer = layerRange.x(); layer <= layerRange.y(); layer++)
	{
		if (canBeMagnified)
		{
			if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.magFilter, prec, coordX, layer, result))
				return true;
		}

		if (canBeMinified)
		{
			const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
			const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
			const int	minTexLevel		= 0;
			const int	maxTexLevel		= texture.getNumLevels()-1;

			DE_ASSERT(minTexLevel <= maxTexLevel);

			if (isLinearMipmap && minTexLevel < maxTexLevel)
			{
				const int		minLevel		= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
				const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);

				DE_ASSERT(minLevel <= maxLevel);

				for (int level = minLevel; level <= maxLevel; level++)
				{
					const float		minF	= de::clamp(minLod - float(level), 0.0f, 1.0f);
					const float		maxF	= de::clamp(maxLod - float(level), 0.0f, 1.0f);

					if (isMipmapLinearSampleResultValid(texture.getLevel(level), texture.getLevel(level+1), sampler, getLevelFilter(sampler.minFilter), prec, coordX, layer, Vec2(minF, maxF), result))
						return true;
				}
			}
			else if (isNearestMipmap)
			{
				// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
				//		 decision to allow floor(lod + 0.5) as well.
				const int		minLevel		= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
				const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);

				DE_ASSERT(minLevel <= maxLevel);

				for (int level = minLevel; level <= maxLevel; level++)
				{
					if (isLevelSampleResultValid(texture.getLevel(level), sampler, getLevelFilter(sampler.minFilter), prec, coordX, layer, result))
						return true;
				}
			}
			else
			{
				if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.minFilter, prec, coordX, layer, result))
					return true;
			}
		}
	}

	return false;
}

bool isLookupResultValid (const Texture2DArrayView& texture, const Sampler& sampler, const LookupPrecision& prec, const Vec3& coord, const Vec2& lodBounds, const Vec4& result)
{
	const IVec2		layerRange		= computeLayerRange(texture.getNumLayers(), prec.coordBits.z(), coord.z());
	const Vec2		coordXY			= coord.swizzle(0,1);
	const float		minLod			= lodBounds.x();
	const float		maxLod			= lodBounds.y();
	const bool		canBeMagnified	= minLod <= sampler.lodThreshold;
	const bool		canBeMinified	= maxLod > sampler.lodThreshold;

	DE_ASSERT(isSamplerSupported(sampler));

	for (int layer = layerRange.x(); layer <= layerRange.y(); layer++)
	{
		if (canBeMagnified)
		{
			if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.magFilter, prec, coordXY, layer, result))
				return true;
		}

		if (canBeMinified)
		{
			const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
			const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
			const int	minTexLevel		= 0;
			const int	maxTexLevel		= texture.getNumLevels()-1;

			DE_ASSERT(minTexLevel <= maxTexLevel);

			if (isLinearMipmap && minTexLevel < maxTexLevel)
			{
				const int		minLevel		= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
				const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);

				DE_ASSERT(minLevel <= maxLevel);

				for (int level = minLevel; level <= maxLevel; level++)
				{
					const float		minF	= de::clamp(minLod - float(level), 0.0f, 1.0f);
					const float		maxF	= de::clamp(maxLod - float(level), 0.0f, 1.0f);

					if (isMipmapLinearSampleResultValid(texture.getLevel(level), texture.getLevel(level+1), sampler, getLevelFilter(sampler.minFilter), prec, coordXY, layer, Vec2(minF, maxF), result))
						return true;
				}
			}
			else if (isNearestMipmap)
			{
				// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
				//		 decision to allow floor(lod + 0.5) as well.
				const int		minLevel		= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
				const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);

				DE_ASSERT(minLevel <= maxLevel);

				for (int level = minLevel; level <= maxLevel; level++)
				{
					if (isLevelSampleResultValid(texture.getLevel(level), sampler, getLevelFilter(sampler.minFilter), prec, coordXY, layer, result))
						return true;
				}
			}
			else
			{
				if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.minFilter, prec, coordXY, layer, result))
					return true;
			}
		}
	}

	return false;
}

static bool isLevelSampleResultValid (const ConstPixelBufferAccess&		level,
									  const Sampler&					sampler,
									  const Sampler::FilterMode			filterMode,
									  const LookupPrecision&			prec,
									  const Vec3&						coord,
									  const Vec4&						result)
{
	if (filterMode == Sampler::LINEAR)
		return isLinearSampleResultValid(level, sampler, prec, coord, result);
	else
		return isNearestSampleResultValid(level, sampler, prec, coord, result);
}

static bool isMipmapLinearSampleResultValid (const ConstPixelBufferAccess&		level0,
										     const ConstPixelBufferAccess&		level1,
											 const Sampler&						sampler,
										     const Sampler::FilterMode			levelFilter,
										     const LookupPrecision&				prec,
										     const Vec3&						coord,
										     const Vec2&						fBounds,
										     const Vec4&						result)
{
	if (levelFilter == Sampler::LINEAR)
		return isLinearMipmapLinearSampleResultValid(level0, level1, sampler, prec, coord, fBounds, result);
	else
		return isNearestMipmapLinearSampleResultValid(level0, level1, sampler, prec, coord, fBounds, result);
}

bool isLookupResultValid (const Texture3DView& texture, const Sampler& sampler, const LookupPrecision& prec, const Vec3& coord, const Vec2& lodBounds, const Vec4& result)
{
	const float		minLod			= lodBounds.x();
	const float		maxLod			= lodBounds.y();
	const bool		canBeMagnified	= minLod <= sampler.lodThreshold;
	const bool		canBeMinified	= maxLod > sampler.lodThreshold;

	DE_ASSERT(isSamplerSupported(sampler));

	if (canBeMagnified)
	{
		if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.magFilter, prec, coord, result))
			return true;
	}

	if (canBeMinified)
	{
		const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
		const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
		const int	minTexLevel		= 0;
		const int	maxTexLevel		= texture.getNumLevels()-1;

		DE_ASSERT(minTexLevel <= maxTexLevel);

		if (isLinearMipmap && minTexLevel < maxTexLevel)
		{
			const int		minLevel		= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
			const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);

			DE_ASSERT(minLevel <= maxLevel);

			for (int level = minLevel; level <= maxLevel; level++)
			{
				const float		minF	= de::clamp(minLod - float(level), 0.0f, 1.0f);
				const float		maxF	= de::clamp(maxLod - float(level), 0.0f, 1.0f);

				if (isMipmapLinearSampleResultValid(texture.getLevel(level), texture.getLevel(level+1), sampler, getLevelFilter(sampler.minFilter), prec, coord, Vec2(minF, maxF), result))
					return true;
			}
		}
		else if (isNearestMipmap)
		{
			// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
			//		 decision to allow floor(lod + 0.5) as well.
			const int		minLevel		= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
			const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);

			DE_ASSERT(minLevel <= maxLevel);

			for (int level = minLevel; level <= maxLevel; level++)
			{
				if (isLevelSampleResultValid(texture.getLevel(level), sampler, getLevelFilter(sampler.minFilter), prec, coord, result))
					return true;
			}
		}
		else
		{
			if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.minFilter, prec, coord, result))
				return true;
		}
	}

	return false;
}

static void getCubeArrayLevelFaces (const TextureCubeArrayView& texture, const int levelNdx, const int layerNdx, ConstPixelBufferAccess (&out)[CUBEFACE_LAST])
{
	const ConstPixelBufferAccess&	level		= texture.getLevel(levelNdx);
	const int						layerDepth	= layerNdx * 6;

	for (int faceNdx = 0; faceNdx < CUBEFACE_LAST; faceNdx++)
	{
		const CubeFace face = (CubeFace)faceNdx;
		out[faceNdx] = getSubregion(level, 0, 0, layerDepth + getCubeArrayFaceIndex(face), level.getWidth(), level.getHeight(), 1);
	}
}

bool isLookupResultValid (const TextureCubeArrayView& texture, const Sampler& sampler, const LookupPrecision& prec, const IVec4& coordBits, const Vec4& coord, const Vec2& lodBounds, const Vec4& result)
{
	const IVec2	layerRange						= computeLayerRange(texture.getNumLayers(), coordBits.w(), coord.w());
	const Vec3	layerCoord						= coord.toWidth<3>();
	int			numPossibleFaces				= 0;
	CubeFace	possibleFaces[CUBEFACE_LAST];

	DE_ASSERT(isSamplerSupported(sampler));

	getPossibleCubeFaces(layerCoord, prec.coordBits, &possibleFaces[0], numPossibleFaces);

	if (numPossibleFaces == 0)
		return true; // Result is undefined.

	for (int layerNdx = layerRange.x(); layerNdx <= layerRange.y(); layerNdx++)
	{
		for (int tryFaceNdx = 0; tryFaceNdx < numPossibleFaces; tryFaceNdx++)
		{
			const CubeFaceFloatCoords	faceCoords		(possibleFaces[tryFaceNdx], projectToFace(possibleFaces[tryFaceNdx], layerCoord));
			const float					minLod			= lodBounds.x();
			const float					maxLod			= lodBounds.y();
			const bool					canBeMagnified	= minLod <= sampler.lodThreshold;
			const bool					canBeMinified	= maxLod > sampler.lodThreshold;

			if (canBeMagnified)
			{
				ConstPixelBufferAccess faces[CUBEFACE_LAST];
				getCubeArrayLevelFaces(texture, 0, layerNdx, faces);

				if (isCubeLevelSampleResultValid(faces, sampler, sampler.magFilter, prec, faceCoords, result))
					return true;
			}

			if (canBeMinified)
			{
				const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
				const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
				const int	minTexLevel		= 0;
				const int	maxTexLevel		= texture.getNumLevels()-1;

				DE_ASSERT(minTexLevel <= maxTexLevel);

				if (isLinearMipmap && minTexLevel < maxTexLevel)
				{
					const int	minLevel	= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
					const int	maxLevel	= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);

					DE_ASSERT(minLevel <= maxLevel);

					for (int levelNdx = minLevel; levelNdx <= maxLevel; levelNdx++)
					{
						const float		minF	= de::clamp(minLod - float(levelNdx), 0.0f, 1.0f);
						const float		maxF	= de::clamp(maxLod - float(levelNdx), 0.0f, 1.0f);

						ConstPixelBufferAccess	faces0[CUBEFACE_LAST];
						ConstPixelBufferAccess	faces1[CUBEFACE_LAST];

						getCubeArrayLevelFaces(texture, levelNdx,		layerNdx,	faces0);
						getCubeArrayLevelFaces(texture, levelNdx + 1,	layerNdx,	faces1);

						if (isCubeMipmapLinearSampleResultValid(faces0, faces1, sampler, getLevelFilter(sampler.minFilter), prec, faceCoords, Vec2(minF, maxF), result))
							return true;
					}
				}
				else if (isNearestMipmap)
				{
					// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
					//		 decision to allow floor(lod + 0.5) as well.
					const int	minLevel	= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
					const int	maxLevel	= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);

					DE_ASSERT(minLevel <= maxLevel);

					for (int levelNdx = minLevel; levelNdx <= maxLevel; levelNdx++)
					{
						ConstPixelBufferAccess faces[CUBEFACE_LAST];
						getCubeArrayLevelFaces(texture, levelNdx, layerNdx, faces);

						if (isCubeLevelSampleResultValid(faces, sampler, getLevelFilter(sampler.minFilter), prec, faceCoords, result))
							return true;
					}
				}
				else
				{
					ConstPixelBufferAccess faces[CUBEFACE_LAST];
					getCubeArrayLevelFaces(texture, 0, layerNdx, faces);

					if (isCubeLevelSampleResultValid(faces, sampler, sampler.minFilter, prec, faceCoords, result))
						return true;
				}
			}
		}
	}

	return false;
}

Vec4 computeFixedPointThreshold (const IVec4& bits)
{
	return computeFixedPointError(bits);
}

Vec4 computeFloatingPointThreshold (const IVec4& bits, const Vec4& value)
{
	return computeFloatingPointError(value, bits);
}

Vec4 computeColorBitsThreshold(const IVec4& bits, const IVec4& numAccurateBits)
{
	return computeColorBitsError(bits, numAccurateBits);
}

Vec2 computeLodBoundsFromDerivates (const float dudx, const float dvdx, const float dwdx, const float dudy, const float dvdy, const float dwdy, const LodPrecision& prec)
{
	const float		mux			= deFloatAbs(dudx);
	const float		mvx			= deFloatAbs(dvdx);
	const float		mwx			= deFloatAbs(dwdx);
	const float		muy			= deFloatAbs(dudy);
	const float		mvy			= deFloatAbs(dvdy);
	const float		mwy			= deFloatAbs(dwdy);

	// Ideal:
	// px = deFloatSqrt2(mux*mux + mvx*mvx + mwx*mwx);
	// py = deFloatSqrt2(muy*muy + mvy*mvy + mwy*mwy);

	// fx, fy estimate lower bounds
	const float		fxMin		= de::max(de::max(mux, mvx), mwx);
	const float		fyMin		= de::max(de::max(muy, mvy), mwy);

	// fx, fy estimate upper bounds
	const float		sqrt2		= deFloatSqrt(2.0f);
	const float		fxMax		= sqrt2 * (mux + mvx + mwx);
	const float		fyMax		= sqrt2 * (muy + mvy + mwy);

	// p = max(px, py) (isotropic filtering)
	const float		pMin		= de::max(fxMin, fyMin);
	const float		pMax		= de::max(fxMax, fyMax);

	// error terms
	const float		pMinErr		= computeFloatingPointError(pMin, prec.derivateBits);
	const float		pMaxErr		= computeFloatingPointError(pMax, prec.derivateBits);

	const float		minLod		= deFloatLog2(pMin-pMinErr);
	const float		maxLod		= deFloatLog2(pMax+pMaxErr);
	const float		lodErr		= computeFixedPointError(prec.lodBits);

	DE_ASSERT(minLod <= maxLod);
	return Vec2(minLod-lodErr, maxLod+lodErr);
}

Vec2 computeLodBoundsFromDerivates (const float dudx, const float dvdx, const float dudy, const float dvdy, const LodPrecision& prec)
{
	return computeLodBoundsFromDerivates(dudx, dvdx, 0.0f, dudy, dvdy, 0.0f, prec);
}

Vec2 computeLodBoundsFromDerivates (const float dudx, const float dudy, const LodPrecision& prec)
{
	return computeLodBoundsFromDerivates(dudx, 0.0f, 0.0f, dudy, 0.0f, 0.0f, prec);
}

Vec2 computeCubeLodBoundsFromDerivates (const Vec3& coord, const Vec3& coordDx, const Vec3& coordDy, const int faceSize, const LodPrecision& prec)
{
	const bool			allowBrokenEdgeDerivate		= false;
	const CubeFace		face						= selectCubeFace(coord);
	int					maNdx						= 0;
	int					sNdx						= 0;
	int					tNdx						= 0;

	// \note Derivate signs don't matter when computing lod
	switch (face)
	{
		case CUBEFACE_NEGATIVE_X:
		case CUBEFACE_POSITIVE_X: maNdx = 0; sNdx = 2; tNdx = 1; break;
		case CUBEFACE_NEGATIVE_Y:
		case CUBEFACE_POSITIVE_Y: maNdx = 1; sNdx = 0; tNdx = 2; break;
		case CUBEFACE_NEGATIVE_Z:
		case CUBEFACE_POSITIVE_Z: maNdx = 2; sNdx = 0; tNdx = 1; break;
		default:
			DE_ASSERT(DE_FALSE);
	}

	{
		const float		sc		= coord[sNdx];
		const float		tc		= coord[tNdx];
		const float		ma		= de::abs(coord[maNdx]);
		const float		scdx	= coordDx[sNdx];
		const float		tcdx	= coordDx[tNdx];
		const float		madx	= de::abs(coordDx[maNdx]);
		const float		scdy	= coordDy[sNdx];
		const float		tcdy	= coordDy[tNdx];
		const float		mady	= de::abs(coordDy[maNdx]);
		const float		dudx	= float(faceSize) * 0.5f * (scdx*ma - sc*madx) / (ma*ma);
		const float		dvdx	= float(faceSize) * 0.5f * (tcdx*ma - tc*madx) / (ma*ma);
		const float		dudy	= float(faceSize) * 0.5f * (scdy*ma - sc*mady) / (ma*ma);
		const float		dvdy	= float(faceSize) * 0.5f * (tcdy*ma - tc*mady) / (ma*ma);
		const Vec2		bounds	= computeLodBoundsFromDerivates(dudx, dvdx, dudy, dvdy, prec);

		// Implementations may compute derivate from projected (s, t) resulting in incorrect values at edges.
		if (allowBrokenEdgeDerivate)
		{
			const Vec3			dxErr		= computeFloatingPointError(coordDx, IVec3(prec.derivateBits));
			const Vec3			dyErr		= computeFloatingPointError(coordDy, IVec3(prec.derivateBits));
			const Vec3			xoffs		= abs(coordDx) + dxErr;
			const Vec3			yoffs		= abs(coordDy) + dyErr;

			if (selectCubeFace(coord + xoffs) != face ||
				selectCubeFace(coord - xoffs) != face ||
				selectCubeFace(coord + yoffs) != face ||
				selectCubeFace(coord - yoffs) != face)
			{
				return Vec2(bounds.x(), 1000.0f);
			}
		}

		return bounds;
	}
}

Vec2 clampLodBounds (const Vec2& lodBounds, const Vec2& lodMinMax, const LodPrecision& prec)
{
	const float lodErr	= computeFixedPointError(prec.lodBits);
	const float	a		= lodMinMax.x();
	const float	b		= lodMinMax.y();
	return Vec2(de::clamp(lodBounds.x(), a-lodErr, b-lodErr), de::clamp(lodBounds.y(), a+lodErr, b+lodErr));
}

bool isLevel1DLookupResultValid (const ConstPixelBufferAccess&	access,
								 const Sampler&					sampler,
								 TexLookupScaleMode				scaleMode,
								 const LookupPrecision&			prec,
								 const float					coordX,
								 const int						coordY,
								 const Vec4&					result)
{
	const Sampler::FilterMode filterMode = scaleMode == TEX_LOOKUP_SCALE_MAGNIFY ? sampler.magFilter : sampler.minFilter;
	return isLevelSampleResultValid(access, sampler, filterMode, prec, coordX, coordY, result);
}

bool isLevel1DLookupResultValid (const ConstPixelBufferAccess&	access,
								 const Sampler&					sampler,
								 TexLookupScaleMode				scaleMode,
								 const IntLookupPrecision&		prec,
								 const float					coordX,
								 const int						coordY,
								 const IVec4&					result)
{
	DE_ASSERT(sampler.minFilter == Sampler::NEAREST && sampler.magFilter == Sampler::NEAREST);
	DE_UNREF(scaleMode);
	return isNearestSampleResultValid(access, sampler, prec, coordX, coordY, result);
}

bool isLevel1DLookupResultValid (const ConstPixelBufferAccess&	access,
								 const Sampler&					sampler,
								 TexLookupScaleMode				scaleMode,
								 const IntLookupPrecision&		prec,
								 const float					coordX,
								 const int						coordY,
								 const UVec4&					result)
{
	DE_ASSERT(sampler.minFilter == Sampler::NEAREST && sampler.magFilter == Sampler::NEAREST);
	DE_UNREF(scaleMode);
	return isNearestSampleResultValid(access, sampler, prec, coordX, coordY, result);
}

bool isLevel2DLookupResultValid (const ConstPixelBufferAccess&	access,
								 const Sampler&					sampler,
								 TexLookupScaleMode				scaleMode,
								 const LookupPrecision&			prec,
								 const Vec2&					coord,
								 const int						coordZ,
								 const Vec4&					result)
{
	const Sampler::FilterMode filterMode = scaleMode == TEX_LOOKUP_SCALE_MAGNIFY ? sampler.magFilter : sampler.minFilter;
	return isLevelSampleResultValid(access, sampler, filterMode, prec, coord, coordZ, result);
}

bool isLevel2DLookupResultValid (const ConstPixelBufferAccess&	access,
								 const Sampler&					sampler,
								 TexLookupScaleMode				scaleMode,
								 const IntLookupPrecision&		prec,
								 const Vec2&					coord,
								 const int						coordZ,
								 const IVec4&					result)
{
	DE_ASSERT(sampler.minFilter == Sampler::NEAREST && sampler.magFilter == Sampler::NEAREST);
	DE_UNREF(scaleMode);
	return isNearestSampleResultValid(access, sampler, prec, coord, coordZ, result);
}

bool isLevel2DLookupResultValid (const ConstPixelBufferAccess&	access,
								 const Sampler&					sampler,
								 TexLookupScaleMode				scaleMode,
								 const IntLookupPrecision&		prec,
								 const Vec2&					coord,
								 const int						coordZ,
								 const UVec4&					result)
{
	DE_ASSERT(sampler.minFilter == Sampler::NEAREST && sampler.magFilter == Sampler::NEAREST);
	DE_UNREF(scaleMode);
	return isNearestSampleResultValid(access, sampler, prec, coord, coordZ, result);
}

bool isLevel3DLookupResultValid (const ConstPixelBufferAccess&	access,
								 const Sampler&					sampler,
								 TexLookupScaleMode				scaleMode,
								 const LookupPrecision&			prec,
								 const Vec3&					coord,
								 const Vec4&					result)
{
	const tcu::Sampler::FilterMode filterMode = scaleMode == TEX_LOOKUP_SCALE_MAGNIFY ? sampler.magFilter : sampler.minFilter;
	return isLevelSampleResultValid(access, sampler, filterMode, prec, coord, result);
}

bool isLevel3DLookupResultValid (const ConstPixelBufferAccess&	access,
								 const Sampler&					sampler,
								 TexLookupScaleMode				scaleMode,
								 const IntLookupPrecision&		prec,
								 const Vec3&					coord,
								 const IVec4&					result)
{
	DE_ASSERT(sampler.minFilter == Sampler::NEAREST && sampler.magFilter == Sampler::NEAREST);
	DE_UNREF(scaleMode);
	return isNearestSampleResultValid(access, sampler, prec, coord, result);
}

bool isLevel3DLookupResultValid (const ConstPixelBufferAccess&	access,
								 const Sampler&					sampler,
								 TexLookupScaleMode				scaleMode,
								 const IntLookupPrecision&		prec,
								 const Vec3&					coord,
								 const UVec4&					result)
{
	DE_ASSERT(sampler.minFilter == Sampler::NEAREST && sampler.magFilter == Sampler::NEAREST);
	DE_UNREF(scaleMode);
	return isNearestSampleResultValid(access, sampler, prec, coord, result);
}

template<typename PrecType, typename ScalarType>
static bool isGatherOffsetsResultValid (const ConstPixelBufferAccess&	level,
										const Sampler&					sampler,
										const PrecType&					prec,
										const Vec2&						coord,
										int								coordZ,
										int								componentNdx,
										const IVec2						(&offsets)[4],
										const Vector<ScalarType, 4>&	result)
{
	const Vec2	uBounds		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(), coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2	vBounds		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getHeight(), coord.y(), prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinate bounds for (x0, y0) - without wrap mode
	const int	minI		= deFloorFloatToInt32(uBounds.x()-0.5f);
	const int	maxI		= deFloorFloatToInt32(uBounds.y()-0.5f);
	const int	minJ		= deFloorFloatToInt32(vBounds.x()-0.5f);
	const int	maxJ		= deFloorFloatToInt32(vBounds.y()-0.5f);

	const int	w			= level.getWidth();
	const int	h			= level.getHeight();

	for (int j = minJ; j <= maxJ; j++)
	{
		for (int i = minI; i <= maxI; i++)
		{
			Vector<ScalarType, 4> color;
			for (int offNdx = 0; offNdx < 4; offNdx++)
			{
				// offNdx-th coordinate offset and then wrapped.
				const int x = wrap(sampler.wrapS, i+offsets[offNdx].x(), w);
				const int y = wrap(sampler.wrapT, j+offsets[offNdx].y(), h);
				color[offNdx] = lookup<ScalarType>(level, sampler, x, y, coordZ)[componentNdx];
			}

			if (isColorValid(prec, color, result))
				return true;
		}
	}

	return false;
}

bool isGatherOffsetsResultValid (const Texture2DView&			texture,
								 const Sampler&					sampler,
								 const LookupPrecision&			prec,
								 const Vec2&					coord,
								 int							componentNdx,
								 const IVec2					(&offsets)[4],
								 const Vec4&					result)
{
	return isGatherOffsetsResultValid(texture.getLevel(0), sampler, prec, coord, 0, componentNdx, offsets, result);
}

bool isGatherOffsetsResultValid (const Texture2DView&			texture,
								 const Sampler&					sampler,
								 const IntLookupPrecision&		prec,
								 const Vec2&					coord,
								 int							componentNdx,
								 const IVec2					(&offsets)[4],
								 const IVec4&					result)
{
	return isGatherOffsetsResultValid(texture.getLevel(0), sampler, prec, coord, 0, componentNdx, offsets, result);
}

bool isGatherOffsetsResultValid (const Texture2DView&			texture,
								 const Sampler&					sampler,
								 const IntLookupPrecision&		prec,
								 const Vec2&					coord,
								 int							componentNdx,
								 const IVec2					(&offsets)[4],
								 const UVec4&					result)
{
	return isGatherOffsetsResultValid(texture.getLevel(0), sampler, prec, coord, 0, componentNdx, offsets, result);
}

template <typename PrecType, typename ScalarType>
static bool is2DArrayGatherOffsetsResultValid (const Texture2DArrayView&		texture,
											   const Sampler&					sampler,
											   const PrecType&					prec,
											   const Vec3&						coord,
											   int								componentNdx,
											   const IVec2						(&offsets)[4],
											   const Vector<ScalarType, 4>&		result)
{
	const IVec2 layerRange = computeLayerRange(texture.getNumLayers(), prec.coordBits.z(), coord.z());
	for (int layer = layerRange.x(); layer <= layerRange.y(); layer++)
	{
		if (isGatherOffsetsResultValid(texture.getLevel(0), sampler, prec, coord.swizzle(0,1), layer, componentNdx, offsets, result))
			return true;
	}
	return false;
}

bool isGatherOffsetsResultValid (const Texture2DArrayView&		texture,
								 const Sampler&					sampler,
								 const LookupPrecision&			prec,
								 const Vec3&					coord,
								 int							componentNdx,
								 const IVec2					(&offsets)[4],
								 const Vec4&					result)
{
	return is2DArrayGatherOffsetsResultValid(texture, sampler, prec, coord, componentNdx, offsets, result);
}

bool isGatherOffsetsResultValid (const Texture2DArrayView&		texture,
								 const Sampler&					sampler,
								 const IntLookupPrecision&		prec,
								 const Vec3&					coord,
								 int							componentNdx,
								 const IVec2					(&offsets)[4],
								 const IVec4&					result)
{
	return is2DArrayGatherOffsetsResultValid(texture, sampler, prec, coord, componentNdx, offsets, result);
}

bool isGatherOffsetsResultValid (const Texture2DArrayView&		texture,
								 const Sampler&					sampler,
								 const IntLookupPrecision&		prec,
								 const Vec3&					coord,
								 int							componentNdx,
								 const IVec2					(&offsets)[4],
								 const UVec4&					result)
{
	return is2DArrayGatherOffsetsResultValid(texture, sampler, prec, coord, componentNdx, offsets, result);
}

template<typename PrecType, typename ScalarType>
static bool isGatherResultValid (const TextureCubeView&			texture,
								 const Sampler&					sampler,
								 const PrecType&				prec,
								 const CubeFaceFloatCoords&		coords,
								 int							componentNdx,
								 const Vector<ScalarType, 4>&	result)
{
	const int	size		= texture.getLevelFace(0, coords.face).getWidth();

	const Vec2	uBounds		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size, coords.s, prec.coordBits.x(), prec.uvwBits.x());
	const Vec2	vBounds		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size, coords.t, prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinate bounds for (x0,y0) - without wrap mode
	const int	minI		= deFloorFloatToInt32(uBounds.x()-0.5f);
	const int	maxI		= deFloorFloatToInt32(uBounds.y()-0.5f);
	const int	minJ		= deFloorFloatToInt32(vBounds.x()-0.5f);
	const int	maxJ		= deFloorFloatToInt32(vBounds.y()-0.5f);

	// Face accesses
	ConstPixelBufferAccess faces[CUBEFACE_LAST];
	for (int face = 0; face < CUBEFACE_LAST; face++)
		faces[face] = texture.getLevelFace(0, CubeFace(face));

	for (int j = minJ; j <= maxJ; j++)
	{
		for (int i = minI; i <= maxI; i++)
		{
			static const IVec2 offsets[4] =
			{
				IVec2(0, 1),
				IVec2(1, 1),
				IVec2(1, 0),
				IVec2(0, 0)
			};

			Vector<ScalarType, 4> color;
			for (int offNdx = 0; offNdx < 4; offNdx++)
			{
				const CubeFaceIntCoords c = remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, i+offsets[offNdx].x(), j+offsets[offNdx].y()), size);
				// If any of samples is out of both edges, implementations can do pretty much anything according to spec.
				// \todo [2014-06-05 nuutti] Test the special case where all corner pixels have exactly the same color.
				//							 See also isSeamlessLinearSampleResultValid and similar.
				if (c.face == CUBEFACE_LAST)
					return true;

				color[offNdx] = lookup<ScalarType>(faces[c.face], sampler, c.s, c.t, 0)[componentNdx];
			}

			if (isColorValid(prec, color, result))
				return true;
		}
	}

	return false;
}

template <typename PrecType, typename ScalarType>
static bool isCubeGatherResultValid (const TextureCubeView&			texture,
									 const Sampler&					sampler,
									 const PrecType&				prec,
									 const Vec3&					coord,
									 int							componentNdx,
									 const Vector<ScalarType, 4>&	result)
{
	int			numPossibleFaces				= 0;
	CubeFace	possibleFaces[CUBEFACE_LAST];

	getPossibleCubeFaces(coord, prec.coordBits, &possibleFaces[0], numPossibleFaces);

	if (numPossibleFaces == 0)
		return true; // Result is undefined.

	for (int tryFaceNdx = 0; tryFaceNdx < numPossibleFaces; tryFaceNdx++)
	{
		const CubeFaceFloatCoords faceCoords(possibleFaces[tryFaceNdx], projectToFace(possibleFaces[tryFaceNdx], coord));

		if (isGatherResultValid(texture, sampler, prec, faceCoords, componentNdx, result))
			return true;
	}

	return false;
}

bool isGatherResultValid (const TextureCubeView&	texture,
						  const Sampler&			sampler,
						  const LookupPrecision&	prec,
						  const Vec3&				coord,
						  int						componentNdx,
						  const Vec4&				result)
{
	return isCubeGatherResultValid(texture, sampler, prec, coord, componentNdx, result);
}

bool isGatherResultValid (const TextureCubeView&		texture,
						  const Sampler&				sampler,
						  const IntLookupPrecision&		prec,
						  const Vec3&					coord,
						  int							componentNdx,
						  const IVec4&					result)
{
	return isCubeGatherResultValid(texture, sampler, prec, coord, componentNdx, result);
}

bool isGatherResultValid (const TextureCubeView&		texture,
						  const Sampler&				sampler,
						  const IntLookupPrecision&		prec,
						  const Vec3&					coord,
						  int							componentNdx,
						  const UVec4&					result)
{
	return isCubeGatherResultValid(texture, sampler, prec, coord, componentNdx, result);
}

} // tcu
